@absolutejs/absolute 0.20.0-beta.110 → 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.
@@ -19401,7 +19401,7 @@ var init_compatibilityDispatcher = __esm(() => {
19401
19401
  // src/cli/scripts/telemetry.ts
19402
19402
  import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync6, writeFileSync } from "fs";
19403
19403
  import { homedir as homedir4 } from "os";
19404
- import { join as join23 } from "path";
19404
+ import { join as join25 } from "path";
19405
19405
  var configDir, configPath, getTelemetryConfig = () => {
19406
19406
  try {
19407
19407
  if (!existsSync5(configPath))
@@ -19414,14 +19414,14 @@ var configDir, configPath, getTelemetryConfig = () => {
19414
19414
  }
19415
19415
  };
19416
19416
  var init_telemetry = __esm(() => {
19417
- configDir = join23(homedir4(), ".absolutejs");
19418
- configPath = join23(configDir, "telemetry.json");
19417
+ configDir = join25(homedir4(), ".absolutejs");
19418
+ configPath = join25(configDir, "telemetry.json");
19419
19419
  });
19420
19420
 
19421
19421
  // src/cli/telemetryEvent.ts
19422
19422
  import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
19423
19423
  import { arch, platform as platform3 } from "os";
19424
- import { dirname as dirname16, join as join24, parse } from "path";
19424
+ import { dirname as dirname18, join as join26, parse } from "path";
19425
19425
  var checkCandidate = (candidate) => {
19426
19426
  if (!existsSync6(candidate)) {
19427
19427
  return null;
@@ -19441,12 +19441,12 @@ var checkCandidate = (candidate) => {
19441
19441
  }, findPackageVersion = () => {
19442
19442
  let { dir } = import.meta;
19443
19443
  while (dir !== parse(dir).root) {
19444
- const candidate = join24(dir, "package.json");
19444
+ const candidate = join26(dir, "package.json");
19445
19445
  const version = checkCandidate(candidate);
19446
19446
  if (version) {
19447
19447
  return version;
19448
19448
  }
19449
- dir = dirname16(dir);
19449
+ dir = dirname18(dir);
19450
19450
  }
19451
19451
  return "unknown";
19452
19452
  }, sendTelemetryEvent = (event, payload) => {
@@ -19487,7 +19487,7 @@ __export(exports_mobilePreview, {
19487
19487
  createAbsoluteMobilePreviewPlugin: () => createAbsoluteMobilePreviewPlugin
19488
19488
  });
19489
19489
  import { Elysia as Elysia3 } from "elysia";
19490
- var ABSOLUTE_MOBILE_PREVIEW_PATH = "/__absolute/mobile-preview", escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;"), isRecord11 = (value) => typeof value === "object" && value !== null, normalizeEntry2 = (entry) => {
19490
+ var ABSOLUTE_MOBILE_PREVIEW_PATH = "/__absolute/mobile-preview", escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;"), isRecord14 = (value) => typeof value === "object" && value !== null, normalizeEntry2 = (entry) => {
19491
19491
  const parsed = new URL(entry ?? "/", "https://absolute.invalid");
19492
19492
  return `${parsed.pathname}${parsed.search}${parsed.hash}`;
19493
19493
  }, absoluteMobilePreviewDocument = (mobile) => {
@@ -19535,7 +19535,7 @@ var ABSOLUTE_MOBILE_PREVIEW_PATH = "/__absolute/mobile-preview", escapeHtml2 = (
19535
19535
  "X-Robots-Tag": "noindex, nofollow"
19536
19536
  }
19537
19537
  })).post("/__absolute/mobile-preview-telemetry", ({ body, status }) => {
19538
- const value = isRecord11(body) ? body : undefined;
19538
+ const value = isRecord14(body) ? body : undefined;
19539
19539
  const durationMs = value?.durationMs;
19540
19540
  const platform4 = value?.platform;
19541
19541
  if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || durationMs > 300000 || platform4 !== "android" && platform4 !== "ios") {
@@ -20092,7 +20092,7 @@ import {
20092
20092
  verify as verify2,
20093
20093
  X509Certificate as X509Certificate3
20094
20094
  } from "crypto";
20095
- import { readFile as readFile29, stat as stat8 } from "fs/promises";
20095
+ import { readFile as readFile30, stat as stat9 } from "fs/promises";
20096
20096
  import path from "path";
20097
20097
  var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, DAY_MS, DEFAULT_MIN_AGE_MS, DEFAULT_GRACE_PERIOD_MS, DEFAULT_RETAIN_RECENT = 5, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", HEALTH_TOKEN_VERSION = 1, HEALTH_KINDS, FAILURE_HEALTH_KINDS, MobileUpdateRegistryError, object6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field2) => {
20098
20098
  if (typeof value !== "string" || value.length === 0)
@@ -21125,7 +21125,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21125
21125
  verifyManifestSignature(manifest, options.publicKeys);
21126
21126
  await assertNotMarked(manifest.appId, manifest.releaseId);
21127
21127
  const localRoot = path.resolve(input.releaseDirectory);
21128
- const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile29(path.join(localRoot, "update.json"), "utf8")));
21128
+ const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile30(path.join(localRoot, "update.json"), "utf8")));
21129
21129
  if (JSON.stringify(localManifest) !== JSON.stringify(manifest))
21130
21130
  throw new MobileUpdateRegistryError("Local mobile update manifest changed");
21131
21131
  const existing = await readManifest(manifest.appId, manifest.releaseId);
@@ -21143,7 +21143,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21143
21143
  if (!existing) {
21144
21144
  for (const file of manifest.files) {
21145
21145
  const local = path.join(localRoot, "files", file.path);
21146
- const metadata = await stat8(local).catch(() => null);
21146
+ const metadata = await stat9(local).catch(() => null);
21147
21147
  if (!metadata?.isFile() || metadata.size !== file.bytes)
21148
21148
  throw new MobileUpdateRegistryError(`Mobile update file ${file.path} size changed`);
21149
21149
  if (await fileDigest(Bun.file(local)) !== file.sha256)
@@ -21411,8 +21411,8 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21411
21411
  });
21412
21412
  }
21413
21413
  const pathname = new URL(request.url).pathname.replace(/^\/+/, "");
21414
- const relative22 = pathname.startsWith(`${route}/`) ? pathname.slice(route.length + 1) : "";
21415
- if (request.method === "POST" && relative22 === "health") {
21414
+ const relative23 = pathname.startsWith(`${route}/`) ? pathname.slice(route.length + 1) : "";
21415
+ if (request.method === "POST" && relative23 === "health") {
21416
21416
  if (!options.registry.recordUpdateHealth)
21417
21417
  return new Response(null, { status: 404 });
21418
21418
  const appId = request.headers.get("x-absolute-mobile-app");
@@ -21455,7 +21455,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21455
21455
  }
21456
21456
  if (request.method !== "GET")
21457
21457
  return new Response(null, { status: 405 });
21458
- if (relative22 === "update.json") {
21458
+ if (relative23 === "update.json") {
21459
21459
  const expoProtocolVersion = request.headers.get("expo-protocol-version");
21460
21460
  const expoProtocol = expoProtocolVersion !== null;
21461
21461
  if (expoProtocol && expoProtocolVersion !== "1")
@@ -21573,7 +21573,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
21573
21573
  }
21574
21574
  });
21575
21575
  }
21576
- const match = /^(amu_[a-f0-9]{64})\/files\/(.+)$/.exec(relative22);
21576
+ const match = /^(amu_[a-f0-9]{64})\/files\/(.+)$/.exec(relative23);
21577
21577
  if (!match?.[1] || !match[2])
21578
21578
  return new Response(null, { status: 404 });
21579
21579
  const file = await options.registry.readUpdateFile({
@@ -21666,15 +21666,15 @@ import {
21666
21666
  createPublicKey as createPublicKey3,
21667
21667
  X509Certificate as X509Certificate4
21668
21668
  } from "crypto";
21669
- import { access as access21, mkdir as mkdir23 } from "fs/promises";
21670
- import { dirname as dirname23, isAbsolute as isAbsolute13, relative as relative22, resolve as resolve24, sep as sep15 } from "path";
21669
+ import { access as access22, mkdir as mkdir24 } from "fs/promises";
21670
+ import { dirname as dirname24, isAbsolute as isAbsolute14, relative as relative23, resolve as resolve25, sep as sep16 } from "path";
21671
21671
  import { pathToFileURL as pathToFileURL5 } from "url";
21672
21672
  import { Elysia as Elysia4 } from "elysia";
21673
- var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE = "mobile.update.ts", object7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), projectPath3 = (projectRoot, requested) => {
21674
- const root = resolve24(projectRoot);
21675
- const path2 = resolve24(root, requested);
21676
- const projectRelative = relative22(root, path2);
21677
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep15}`) || isAbsolute13(projectRelative))
21673
+ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE = "mobile.update.ts", object7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), projectPath4 = (projectRoot, requested) => {
21674
+ const root = resolve25(projectRoot);
21675
+ const path2 = resolve25(root, requested);
21676
+ const projectRelative = relative23(root, path2);
21677
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep16}`) || isAbsolute14(projectRelative))
21678
21678
  throw new TypeError("mobile.updates.server.registry must remain inside the project.");
21679
21679
  return path2;
21680
21680
  }, isRegistry4 = (value) => object7(value) && [
@@ -21696,8 +21696,8 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
21696
21696
  };
21697
21697
  return metadata;
21698
21698
  }, loadAbsoluteMobileUpdateServerModule = async (projectRoot, requestedModulePath = DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE) => {
21699
- const modulePath = projectPath3(projectRoot, requestedModulePath);
21700
- await access21(modulePath).catch(() => {
21699
+ const modulePath = projectPath4(projectRoot, requestedModulePath);
21700
+ await access22(modulePath).catch(() => {
21701
21701
  throw new TypeError(`Mobile update server registry does not exist: ${modulePath}. Run \`absolute mobile update provision\`.`);
21702
21702
  });
21703
21703
  const loaded = await import(pathToFileURL5(modulePath).href);
@@ -21884,15 +21884,15 @@ export default createMobileUpdateRegistry({
21884
21884
  `, `export default createMobileUpdateRegistry({
21885
21885
  ${generated}`);
21886
21886
  }, writeAbsoluteMobileUpdateRegistry = async (options) => {
21887
- const path2 = projectPath3(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
21887
+ const path2 = projectPath4(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
21888
21888
  if (!options.force) {
21889
- await access21(path2).then(() => {
21889
+ await access22(path2).then(() => {
21890
21890
  throw new TypeError(`Mobile update registry already exists: ${path2}. Pass --force to replace it.`);
21891
21891
  }, () => {
21892
21892
  return;
21893
21893
  });
21894
21894
  }
21895
- await mkdir23(dirname23(path2), { recursive: true });
21895
+ await mkdir24(dirname24(path2), { recursive: true });
21896
21896
  await Bun.write(path2, renderAbsoluteMobileUpdateRegistry({
21897
21897
  ...options.health ? { health: options.health } : {},
21898
21898
  ...options.rollout ? { rollout: options.rollout } : {},
@@ -33029,140 +33029,1130 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
33029
33029
  // src/mobile/index.ts
33030
33030
  init_config();
33031
33031
 
33032
- // src/mobile/ciWorkflow.ts
33033
- import { existsSync as existsSync4 } from "fs";
33034
- import { access as access16, mkdir as mkdir16, readFile as readFile20, writeFile as writeFile16 } from "fs/promises";
33035
- import { dirname as dirname15, extname as extname5, relative as relative15, resolve as resolve17, sep as sep10 } from "path";
33036
- var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 2;
33037
- var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
33038
- var CI_ENV_INDENTATION = 6;
33039
- var RESERVED_SECRET_NAMES = new Set([
33040
- "ABSOLUTE_ANDROID_KEYSTORE_BASE64",
33041
- "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
33042
- "ABSOLUTE_ANDROID_KEY_ALIAS",
33043
- "ABSOLUTE_ANDROID_KEY_PASSWORD",
33044
- "ABSOLUTE_GOOGLE_CREDENTIALS_BASE64",
33045
- "ABSOLUTE_IOS_CERTIFICATE_BASE64",
33046
- "ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
33047
- "ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
33048
- "ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
33049
- "ABSOLUTE_IOS_DEVELOPMENT_TEAM",
33050
- "APP_STORE_CONNECT_ISSUER_ID",
33051
- "APP_STORE_CONNECT_KEY_ID",
33052
- "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
33053
- ]);
33032
+ // src/mobile/ciPromotionAudit.ts
33033
+ import {
33034
+ access as access18,
33035
+ mkdir as mkdir18,
33036
+ mkdtemp as mkdtemp11,
33037
+ readFile as readFile22,
33038
+ readdir as readdir7,
33039
+ rename as rename14,
33040
+ rm as rm14,
33041
+ stat as stat7,
33042
+ writeFile as writeFile18
33043
+ } from "fs/promises";
33044
+ import { dirname as dirname16, isAbsolute as isAbsolute11, join as join24, relative as relative17, resolve as resolve19, sep as sep12 } from "path";
33045
+
33046
+ // src/mobile/certificationVerification.ts
33047
+ import { access as access16, mkdir as mkdir16, readFile as readFile20, stat as stat6, writeFile as writeFile16 } from "fs/promises";
33048
+ import { dirname as dirname15, isAbsolute as isAbsolute9, relative as relative15, resolve as resolve17, sep as sep10 } from "path";
33049
+ var ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT = 1;
33050
+ var MAX_VERIFICATION_BYTES = 1048576;
33051
+ var SHA_PATTERN = /^[a-f0-9]{40}$/u;
33052
+ var REPOSITORY_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
33053
+ var REF_PATTERN = /^refs\/(?:heads|tags)\/[^@\s]+$/u;
33054
+ var WORKFLOW_PATTERN = /^\.github\/workflows\/[^/@]+\.ya?ml$/u;
33055
+ var isRecord11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
33056
+ var required = (value, name) => {
33057
+ if (typeof value !== "string" || value.trim() === "")
33058
+ throw new TypeError(`${name} is required.`);
33059
+ return value.trim();
33060
+ };
33061
+ var requireMatch = (value, pattern, name) => {
33062
+ if (!pattern.test(value))
33063
+ throw new TypeError(`${name} is invalid.`);
33064
+ return value;
33065
+ };
33066
+ var createAbsoluteMobileCertificationVerification = (bundle, environment) => {
33067
+ const repository = required(environment.GITHUB_REPOSITORY, "GITHUB_REPOSITORY");
33068
+ const workflowReference = required(environment.GITHUB_WORKFLOW_REF, "GITHUB_WORKFLOW_REF");
33069
+ const prefix = `${repository}/`;
33070
+ const separator = workflowReference.lastIndexOf("@");
33071
+ if (!workflowReference.startsWith(prefix) || separator <= prefix.length)
33072
+ throw new TypeError("GITHUB_WORKFLOW_REF is invalid.");
33073
+ return parseAbsoluteMobileCertificationVerification({
33074
+ bundle,
33075
+ format: ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
33076
+ identity: {
33077
+ issuer: "https://token.actions.githubusercontent.com",
33078
+ ref: environment.GITHUB_REF,
33079
+ repository,
33080
+ sha: environment.GITHUB_SHA,
33081
+ workflowPath: workflowReference.slice(prefix.length, separator)
33082
+ },
33083
+ kind: "sigstore-bundle"
33084
+ });
33085
+ };
33086
+ var parseAbsoluteMobileCertificationVerification = (value) => {
33087
+ if (!isRecord11(value) || value.format !== ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT || value.kind !== "sigstore-bundle" || !isRecord11(value.bundle) || !isRecord11(value.identity))
33088
+ throw new TypeError("Mobile certification verification contract is invalid.");
33089
+ const issuer = required(value.identity.issuer, "verification issuer");
33090
+ if (issuer !== "https://token.actions.githubusercontent.com")
33091
+ throw new TypeError("verification issuer is invalid.");
33092
+ const verification = {
33093
+ bundle: value.bundle,
33094
+ format: ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
33095
+ identity: {
33096
+ issuer,
33097
+ ref: requireMatch(required(value.identity.ref, "verification ref"), REF_PATTERN, "verification ref"),
33098
+ repository: requireMatch(required(value.identity.repository, "verification repository"), REPOSITORY_PATTERN, "verification repository"),
33099
+ sha: requireMatch(required(value.identity.sha, "verification sha"), SHA_PATTERN, "verification sha"),
33100
+ workflowPath: requireMatch(required(value.identity.workflowPath, "verification workflowPath"), WORKFLOW_PATTERN, "verification workflowPath")
33101
+ },
33102
+ kind: "sigstore-bundle"
33103
+ };
33104
+ const bytes = new TextEncoder().encode(JSON.stringify(verification));
33105
+ if (bytes.byteLength > MAX_VERIFICATION_BYTES)
33106
+ throw new TypeError("Mobile certification verification exceeds the 1 MiB limit.");
33107
+ return verification;
33108
+ };
33109
+ var projectFile = (projectRoot, requested) => {
33110
+ const root = resolve17(projectRoot);
33111
+ const path = resolve17(root, requested);
33112
+ const portable = relative15(root, path);
33113
+ if (portable === ".." || portable.startsWith(`..${sep10}`) || isAbsolute9(portable))
33114
+ throw new TypeError("Mobile certification verification path must remain inside the project.");
33115
+ return path;
33116
+ };
33117
+ var readAbsoluteMobileCertificationVerification = async (projectRoot, requested) => {
33118
+ const path = projectFile(projectRoot, requested);
33119
+ await access16(path).catch(() => {
33120
+ throw new TypeError(`Mobile certification verification does not exist: ${path}`);
33121
+ });
33122
+ const metadata = await stat6(path);
33123
+ if (!metadata.isFile() || metadata.size > MAX_VERIFICATION_BYTES)
33124
+ throw new TypeError("Mobile certification verification file is invalid.");
33125
+ return parseAbsoluteMobileCertificationVerification(JSON.parse(await readFile20(path, "utf8")));
33126
+ };
33127
+ var readAbsoluteSigstoreBundle = async (projectRoot, requested) => {
33128
+ const path = projectFile(projectRoot, requested);
33129
+ const metadata = await stat6(path).catch(() => null);
33130
+ if (!metadata?.isFile() || metadata.size > MAX_VERIFICATION_BYTES)
33131
+ throw new TypeError("Sigstore bundle file is invalid.");
33132
+ const value = JSON.parse(await readFile20(path, "utf8"));
33133
+ if (!isRecord11(value))
33134
+ throw new TypeError("Sigstore bundle is invalid.");
33135
+ return value;
33136
+ };
33137
+ var writeAbsoluteMobileCertificationVerification = async (projectRoot, requested, verification) => {
33138
+ const path = projectFile(projectRoot, requested);
33139
+ const parsed = parseAbsoluteMobileCertificationVerification(verification);
33140
+ await mkdir16(dirname15(path), { recursive: true });
33141
+ await writeFile16(path, `${JSON.stringify(parsed, null, 2)}
33142
+ `, {
33143
+ mode: 384
33144
+ });
33145
+ return path;
33146
+ };
33147
+
33148
+ // src/mobile/releaseCertification.ts
33149
+ import { createHash as createHash16 } from "crypto";
33150
+ import {
33151
+ access as access17,
33152
+ mkdir as mkdir17,
33153
+ mkdtemp as mkdtemp10,
33154
+ readFile as readFile21,
33155
+ rename as rename13,
33156
+ rm as rm13,
33157
+ writeFile as writeFile17
33158
+ } from "fs/promises";
33159
+ import { basename as basename8, isAbsolute as isAbsolute10, join as join23, relative as relative16, resolve as resolve18, sep as sep11 } from "path";
33160
+ var ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT = 1;
33161
+ var CERTIFICATION_PREFIX = "amobile_cert_";
33162
+ var SHA256 = /^[a-f0-9]{64}$/u;
33163
+ var STRENGTH_RANK = {
33164
+ device: 1,
33165
+ installed: 0,
33166
+ simulator: 0,
33167
+ store: 2
33168
+ };
33169
+ var isRequirement = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
33170
+ var isStrength = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
33171
+ var isArtifactExactness = (value) => value === "archive-equivalent" || value === "source-equivalent" || value === "store-delivered";
33172
+ var isDistribution = (value) => value === "apple-processed" || value === "registered-device" || value === "simulator-release";
33173
+ var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
33174
+ var inside4 = (root, candidate) => {
33175
+ const path = relative16(resolve18(root), resolve18(candidate));
33176
+ return path === "" || !isAbsolute10(path) && path !== ".." && !path.startsWith(`..${sep11}`);
33177
+ };
33054
33178
  var exists8 = async (path) => {
33055
33179
  try {
33056
- await access16(path);
33180
+ await access17(path);
33057
33181
  return true;
33058
33182
  } catch {
33059
33183
  return false;
33060
33184
  }
33061
33185
  };
33062
- var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
33063
- var projectPath = (projectRoot, value, field2, options = {}) => {
33064
- const root = resolve17(projectRoot);
33065
- const path = resolve17(root, value);
33066
- const portable = relative15(root, path).replaceAll("\\", "/");
33067
- if (portable === ".." || portable.startsWith(`..${sep10}`) || portable.startsWith("../") || portable === "") {
33068
- throw new TypeError(`${field2} must remain inside the project root.`);
33069
- }
33070
- if (/\r|\n/u.test(portable) || portable.startsWith("-"))
33071
- throw new TypeError(`${field2} contains an unsafe path.`);
33072
- if (!options.allowMissing && !existsSync4(path))
33073
- throw new TypeError(`${field2} does not exist inside the project.`);
33074
- return portable;
33186
+ var sha2564 = (bytes) => createHash16("sha256").update(bytes).digest("hex");
33187
+ var requireString2 = (value, name) => {
33188
+ if (typeof value !== "string" || !value)
33189
+ throw new TypeError(`Mobile certification ${name} is invalid.`);
33190
+ return value;
33075
33191
  };
33076
- var workflowOutputPath = (projectRoot, value) => {
33077
- const root = resolve17(projectRoot);
33078
- const workflows = resolve17(root, ".github/workflows");
33079
- const path = resolve17(root, value ?? ".github/workflows/absolute-mobile.yml");
33080
- const portable = relative15(workflows, path);
33081
- if (portable === ".." || portable.startsWith(`..${sep10}`) || extname5(path) !== ".yml" && extname5(path) !== ".yaml") {
33082
- throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
33083
- }
33084
- return path;
33192
+ var requirePositiveInteger = (value, name) => {
33193
+ if (!Number.isSafeInteger(value) || Number(value) < 1)
33194
+ throw new TypeError(`Mobile certification ${name} is invalid.`);
33195
+ return Number(value);
33085
33196
  };
33086
- var normalizeSecretEnvironment = (values = []) => {
33087
- const names = [...new Set(values)].sort();
33088
- for (const name of names) {
33089
- if (!SECRET_NAME_PATTERN.test(name))
33090
- throw new TypeError("mobile ci github --secret-env values must be uppercase environment variable names.");
33091
- if (name.startsWith("GITHUB_") || name.startsWith("RUNNER_") || name.startsWith("ACTIONS_") || RESERVED_SECRET_NAMES.has(name)) {
33092
- throw new TypeError(`mobile ci github --secret-env cannot replace reserved variable ${name}.`);
33093
- }
33094
- }
33095
- return names;
33197
+ var resolveEvidencePath = async (projectRoot, requested) => {
33198
+ const selected = resolve18(projectRoot, requested);
33199
+ if (!inside4(projectRoot, selected))
33200
+ throw new TypeError("Mobile certification evidence must remain inside the project.");
33201
+ if (basename8(selected) === "report.json")
33202
+ return selected;
33203
+ const report = join23(selected, "report.json");
33204
+ if (!await exists8(report))
33205
+ throw new TypeError(`Mobile certification evidence does not contain report.json: ${requested}`);
33206
+ return report;
33096
33207
  };
33097
- var customSecretEnvironment = (names, indentation = CI_ENV_INDENTATION) => names.map((name) => `${" ".repeat(indentation)}${name}: \${{ secrets.${name} }}`).join(`
33098
- `);
33099
- var commandEnvironment2 = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
33100
- ABSOLUTE_REGISTRY_MODULE: ${yamlString(options.registryModule)}
33101
- ABSOLUTE_SERVER_ENTRY: ${yamlString(options.serverEntry)}`;
33102
- var appendConfigArgument = `if [[ -n "$ABSOLUTE_CONFIG_PATH" ]]; then
33103
- args+=(--config "$ABSOLUTE_CONFIG_PATH")
33104
- fi`;
33105
- var installSteps = ` - name: Check out source
33106
- uses: actions/checkout@v6
33107
- - name: Install Bun
33108
- uses: oven-sh/setup-bun@v2
33109
- - name: Install exact dependencies
33110
- run: bun ci`;
33111
- var bundleAuditSteps = ` - name: Prepare production mobile bundle
33112
- shell: bash
33113
- run: |
33114
- args=(bunx absolute prepare "$ABSOLUTE_SERVER_ENTRY" --outdir .absolutejs/mobile-ci/server)
33115
- ${appendConfigArgument}
33116
- "\${args[@]}"
33117
- - name: Run cryptographic mobile bundle audit
33118
- id: mobile-bundle-audit
33119
- continue-on-error: true
33120
- shell: bash
33121
- run: |
33122
- mkdir -p .absolutejs/mobile-ci
33123
- args=(bunx absolute mobile inspect --json --require-bundle)
33124
- ${appendConfigArgument}
33125
- "\${args[@]}" > .absolutejs/mobile-ci/inspection.json
33126
- - name: Upload mobile inspection report
33127
- if: always()
33128
- uses: actions/upload-artifact@v7
33129
- with:
33130
- name: absolute-mobile-inspection
33131
- path: .absolutejs/mobile-ci/inspection.json
33132
- if-no-files-found: error
33133
- retention-days: 30
33134
- include-hidden-files: true
33135
- - name: Enforce mobile bundle audit
33136
- if: steps.mobile-bundle-audit.outcome != 'success'
33137
- run: exit 1`;
33138
- var releaseAuditSteps = (platform3) => ` - name: Run redacted mobile release audit
33139
- id: mobile-release-audit
33140
- continue-on-error: true
33141
- shell: bash
33142
- run: |
33143
- mkdir -p .absolutejs/mobile-ci
33144
- args=(bunx absolute mobile doctor release ${platform3} --json)
33145
- ${appendConfigArgument}
33146
- "\${args[@]}" > .absolutejs/mobile-ci/compliance.json
33147
- - name: Upload mobile compliance report
33148
- if: always()
33149
- uses: actions/upload-artifact@v7
33150
- with:
33151
- name: absolute-mobile-compliance-\${{ github.job }}
33152
- path: .absolutejs/mobile-ci/compliance.json
33153
- if-no-files-found: error
33154
- retention-days: 30
33155
- include-hidden-files: true
33156
- - name: Enforce mobile release audit
33157
- if: steps.mobile-release-audit.outcome != 'success'
33158
- run: exit 1`;
33159
- var platformInput = (platforms) => {
33160
- const choices = platforms.length === 2 ? ["all", ...platforms] : platforms;
33161
- return ` platform:
33162
- description: Native platform to build
33163
- required: true
33164
- type: choice
33165
- default: ${choices[0]}
33208
+ var releaseIdentity = (release) => {
33209
+ const { metadata } = release;
33210
+ if (!metadata.signed)
33211
+ throw new TypeError("Mobile release certification requires a signed native release.");
33212
+ const common = {
33213
+ appBuild: metadata.appBuild,
33214
+ appId: metadata.appId,
33215
+ artifactBytes: metadata.bytes,
33216
+ artifactSha256: metadata.sha256,
33217
+ engine: metadata.engine,
33218
+ platform: metadata.platform,
33219
+ releaseId: metadata.releaseId,
33220
+ runtime: metadata.runtime,
33221
+ signed: true
33222
+ };
33223
+ if (metadata.platform === "android")
33224
+ return {
33225
+ ...common,
33226
+ ...metadata.versionCode === undefined ? {} : { versionCode: metadata.versionCode }
33227
+ };
33228
+ return {
33229
+ ...common,
33230
+ ...metadata.buildNumber === undefined ? {} : { buildNumber: metadata.buildNumber },
33231
+ marketingVersion: metadata.marketingVersion
33232
+ };
33233
+ };
33234
+ var requireCheck = (report, id, accepted = ["PASS"]) => {
33235
+ const checks = report.automatedChecks;
33236
+ if (!Array.isArray(checks))
33237
+ throw new TypeError("Mobile certification evidence has invalid automated checks.");
33238
+ const check = checks.find((candidate) => isRecord12(candidate) && candidate.id === id);
33239
+ if (!isRecord12(check) || !accepted.includes(String(check.result)))
33240
+ throw new TypeError(`Mobile certification evidence does not satisfy ${id}.`);
33241
+ };
33242
+ var validateCommonReport = (report, release) => {
33243
+ if (report.reportVersion !== 1 || report.platform !== release.platform || !isRecord12(report.metadata) || report.metadata.provider !== release.engine || !isRecord12(report.run) || report.run.status !== "pass" || report.run.appId !== release.appId)
33244
+ throw new TypeError("Mobile certification evidence does not match the release platform, provider, application, or successful-run contract.");
33245
+ requireCheck(report, "AUTO-DEV-01");
33246
+ return {
33247
+ generatedAt: requireString2(report.generatedAt, "evidence generatedAt"),
33248
+ run: report.run
33249
+ };
33250
+ };
33251
+ var validateReleaseFields = (value, release) => {
33252
+ if (value.releaseId !== release.releaseId || value.artifactSha256 !== release.artifactSha256 || value.artifactBytes !== release.artifactBytes || value.engine !== release.engine || value.signed !== true)
33253
+ throw new TypeError("Mobile certification evidence does not match the immutable release identity.");
33254
+ };
33255
+ var validateAndroidEvidence = (report, run, release, reportSha256, generatedAt) => {
33256
+ if (!isRecord12(run.release))
33257
+ throw new TypeError("Android certification evidence has no installed release result.");
33258
+ validateReleaseFields(run.release, release);
33259
+ if (run.release.embeddedOffline !== true)
33260
+ throw new TypeError("Android certification evidence did not prove embedded offline relaunch.");
33261
+ requireCheck(report, "AUTO-RELEASE-01");
33262
+ requireCheck(report, "AUTO-RELEASE-OFFLINE-01");
33263
+ return {
33264
+ generatedAt,
33265
+ networkUnavailable: "proven",
33266
+ remote: false,
33267
+ reportSha256,
33268
+ strength: "installed"
33269
+ };
33270
+ };
33271
+ var validateIosEvidence = (report, run, release, reportSha256, generatedAt) => {
33272
+ if (!isRecord12(run.iosRelease))
33273
+ throw new TypeError("iOS certification evidence has no installed release result.");
33274
+ const ios = run.iosRelease;
33275
+ validateReleaseFields(ios, release);
33276
+ if (ios.embeddedLocal !== true)
33277
+ throw new TypeError("iOS certification evidence did not prove embedded local rendering.");
33278
+ requireCheck(report, "AUTO-IOS-RELEASE-01");
33279
+ let strength;
33280
+ if (ios.distribution === "simulator-release" && ios.artifactExactness === "source-equivalent" && run.targetKind === "simulator" && ios.networkUnavailable === "not-proven")
33281
+ strength = "simulator";
33282
+ else if (ios.distribution === "registered-device" && ios.artifactExactness === "archive-equivalent" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
33283
+ strength = "device";
33284
+ else if (ios.distribution === "apple-processed" && ios.artifactExactness === "store-delivered" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
33285
+ strength = "store";
33286
+ else
33287
+ throw new TypeError("iOS certification evidence contains an invalid target, distribution, exactness, or offline-strength combination.");
33288
+ if (strength === "simulator")
33289
+ requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01", ["SKIPPED"]);
33290
+ else
33291
+ requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01");
33292
+ return {
33293
+ artifactExactness: ios.artifactExactness,
33294
+ distribution: ios.distribution,
33295
+ generatedAt,
33296
+ networkUnavailable: ios.networkUnavailable === "user-confirmed" ? "proven" : "not-proven",
33297
+ remote: ios.remote === true,
33298
+ reportSha256,
33299
+ strength
33300
+ };
33301
+ };
33302
+ var validateEvidence = async (projectRoot, requested, release) => {
33303
+ const path = await resolveEvidencePath(projectRoot, requested);
33304
+ const bytes = await readFile21(path);
33305
+ let value;
33306
+ try {
33307
+ value = JSON.parse(bytes.toString("utf8"));
33308
+ } catch {
33309
+ throw new TypeError("Mobile certification evidence report is not valid JSON.");
33310
+ }
33311
+ if (!isRecord12(value))
33312
+ throw new TypeError("Mobile certification evidence report must contain an object.");
33313
+ const { generatedAt, run } = validateCommonReport(value, release);
33314
+ const digest = sha2564(bytes);
33315
+ return release.platform === "android" ? validateAndroidEvidence(value, run, release, digest, generatedAt) : validateIosEvidence(value, run, release, digest, generatedAt);
33316
+ };
33317
+ var defaultRequirement = (platform3) => platform3 === "android" ? "installed" : "simulator";
33318
+ var validateRequirement = (platform3, requirement) => {
33319
+ if (platform3 === "android" && requirement !== "installed")
33320
+ throw new TypeError("Android certification currently uses its fixed installed/offline requirement; --require simulator, device, and store are iOS policies.");
33321
+ if (platform3 === "ios" && requirement === "installed")
33322
+ throw new TypeError("iOS certification requires simulator, device, or store evidence.");
33323
+ };
33324
+ var satisfiesRequirement = (strength, requirement) => {
33325
+ if (requirement === "installed")
33326
+ return strength === "installed";
33327
+ if (strength === "installed")
33328
+ return false;
33329
+ return STRENGTH_RANK[strength] >= STRENGTH_RANK[requirement];
33330
+ };
33331
+ var resolveAbsoluteMobileCertificationRequirement = (config, target) => {
33332
+ const channelRequirement = target.channel ? config.releaseCertification.channels[target.channel]?.[target.platform] : undefined;
33333
+ const playRequirement = target.googlePlayTrack ? config.releaseCertification.googlePlayTracks[target.googlePlayTrack] : undefined;
33334
+ const requirements = [channelRequirement, playRequirement].filter((value) => value !== undefined);
33335
+ if (requirements.length === 0)
33336
+ return;
33337
+ return requirements.reduce((strongest, candidate) => STRENGTH_RANK[candidate] > STRENGTH_RANK[strongest] ? candidate : strongest);
33338
+ };
33339
+ var validateCertifiedEvidence = (platform3, evidence) => {
33340
+ if (platform3 === "android") {
33341
+ if (evidence.strength !== "installed" || evidence.networkUnavailable !== "proven" || evidence.artifactExactness !== undefined || evidence.distribution !== undefined || evidence.remote)
33342
+ throw new TypeError("Android release certification contains invalid evidence strength.");
33343
+ return;
33344
+ }
33345
+ const simulator = evidence.strength === "simulator" && evidence.artifactExactness === "source-equivalent" && evidence.distribution === "simulator-release" && evidence.networkUnavailable === "not-proven";
33346
+ const device = evidence.strength === "device" && evidence.artifactExactness === "archive-equivalent" && evidence.distribution === "registered-device" && evidence.networkUnavailable === "proven";
33347
+ const store = evidence.strength === "store" && evidence.artifactExactness === "store-delivered" && evidence.distribution === "apple-processed" && evidence.networkUnavailable === "proven";
33348
+ if (!simulator && !device && !store)
33349
+ throw new TypeError("iOS release certification contains invalid evidence strength.");
33350
+ };
33351
+ var canonicalJsonValue = (value) => {
33352
+ if (Array.isArray(value))
33353
+ return value.map(canonicalJsonValue);
33354
+ if (!isRecord12(value))
33355
+ return value;
33356
+ const sorted = {};
33357
+ for (const key of Object.keys(value).sort())
33358
+ sorted[key] = canonicalJsonValue(value[key]);
33359
+ return sorted;
33360
+ };
33361
+ var certificationBody = (certification) => JSON.stringify(canonicalJsonValue(certification));
33362
+ var createAbsoluteMobileReleaseCertification = async (options) => {
33363
+ if (options.evidencePaths.length === 0)
33364
+ throw new TypeError("Mobile release certification requires at least one --evidence report.");
33365
+ const release = releaseIdentity(options.release);
33366
+ const requirement = options.requirement ?? defaultRequirement(release.platform);
33367
+ validateRequirement(release.platform, requirement);
33368
+ const evidence = await Promise.all(options.evidencePaths.map((path) => validateEvidence(options.projectRoot, path, release)));
33369
+ evidence.sort((left, right) => left.reportSha256.localeCompare(right.reportSha256));
33370
+ const [firstEvidence] = evidence;
33371
+ if (!firstEvidence)
33372
+ throw new TypeError("Mobile release certification requires valid evidence.");
33373
+ const strength = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, firstEvidence.strength);
33374
+ if (!satisfiesRequirement(strength, requirement))
33375
+ throw new TypeError(`Mobile release evidence strength ${strength} does not satisfy required ${requirement} certification.`);
33376
+ const body = {
33377
+ evidence,
33378
+ format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
33379
+ generatedAt: options.generatedAt ?? evidence.reduce((latest, candidate) => candidate.generatedAt > latest ? candidate.generatedAt : latest, firstEvidence.generatedAt),
33380
+ release,
33381
+ requirement,
33382
+ status: "certified",
33383
+ strength
33384
+ };
33385
+ return {
33386
+ certificationId: `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`,
33387
+ ...body
33388
+ };
33389
+ };
33390
+ var parseCertification = (value) => {
33391
+ if (!isRecord12(value) || value.format !== ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT || value.status !== "certified" || !isRecord12(value.release) || !Array.isArray(value.evidence))
33392
+ throw new TypeError("Mobile release certification contract is invalid.");
33393
+ const { release } = value;
33394
+ const { platform: platform3 } = release;
33395
+ const { engine } = release;
33396
+ if (platform3 !== "android" && platform3 !== "ios" || engine !== "capacitor" && engine !== "expo" || release.signed !== true)
33397
+ throw new TypeError("Mobile release certification identity is invalid.");
33398
+ const { requirement } = value;
33399
+ const { strength } = value;
33400
+ if (!isRequirement(requirement) || !isStrength(strength))
33401
+ throw new TypeError("Mobile release certification policy is invalid.");
33402
+ validateRequirement(platform3, requirement);
33403
+ if (!satisfiesRequirement(strength, requirement))
33404
+ throw new TypeError("Mobile release certification does not satisfy its declared policy.");
33405
+ const artifactBytes = requirePositiveInteger(release.artifactBytes, "release artifactBytes");
33406
+ const artifactSha256 = requireString2(release.artifactSha256, "release artifactSha256");
33407
+ if (!SHA256.test(artifactSha256))
33408
+ throw new TypeError("Mobile release certification artifact digest is invalid.");
33409
+ const parsedRelease = {
33410
+ appBuild: requireString2(release.appBuild, "release appBuild"),
33411
+ appId: requireString2(release.appId, "release appId"),
33412
+ artifactBytes,
33413
+ artifactSha256,
33414
+ engine,
33415
+ platform: platform3,
33416
+ releaseId: requireString2(release.releaseId, "release releaseId"),
33417
+ runtime: requireString2(release.runtime, "release runtime"),
33418
+ signed: true,
33419
+ ...release.buildNumber === undefined ? {} : {
33420
+ buildNumber: requirePositiveInteger(release.buildNumber, "release buildNumber")
33421
+ },
33422
+ ...release.marketingVersion === undefined ? {} : {
33423
+ marketingVersion: requireString2(release.marketingVersion, "release marketingVersion")
33424
+ },
33425
+ ...release.versionCode === undefined ? {} : {
33426
+ versionCode: requirePositiveInteger(release.versionCode, "release versionCode")
33427
+ }
33428
+ };
33429
+ const evidence = value.evidence.map((candidate) => {
33430
+ if (!isRecord12(candidate) || !SHA256.test(String(candidate.reportSha256)) || !isStrength(candidate.strength) || candidate.networkUnavailable !== "not-proven" && candidate.networkUnavailable !== "proven" || typeof candidate.remote !== "boolean" || typeof candidate.generatedAt !== "string" || candidate.artifactExactness !== undefined && !isArtifactExactness(candidate.artifactExactness) || candidate.distribution !== undefined && !isDistribution(candidate.distribution))
33431
+ throw new TypeError("Mobile release certification evidence is invalid.");
33432
+ const parsedEvidence = {
33433
+ generatedAt: candidate.generatedAt,
33434
+ networkUnavailable: candidate.networkUnavailable,
33435
+ remote: candidate.remote,
33436
+ reportSha256: String(candidate.reportSha256),
33437
+ strength: candidate.strength,
33438
+ ...candidate.artifactExactness === undefined ? {} : {
33439
+ artifactExactness: candidate.artifactExactness
33440
+ },
33441
+ ...candidate.distribution === undefined ? {} : { distribution: candidate.distribution }
33442
+ };
33443
+ validateCertifiedEvidence(platform3, parsedEvidence);
33444
+ return parsedEvidence;
33445
+ });
33446
+ const strongestEvidence = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, evidence[0]?.strength ?? "installed");
33447
+ if (evidence.length === 0 || strongestEvidence !== strength)
33448
+ throw new TypeError("Mobile release certification evidence does not support its declared strength.");
33449
+ const parsed = {
33450
+ certificationId: requireString2(value.certificationId, "certificationId"),
33451
+ evidence,
33452
+ format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
33453
+ generatedAt: requireString2(value.generatedAt, "generatedAt"),
33454
+ release: parsedRelease,
33455
+ requirement,
33456
+ status: "certified",
33457
+ strength
33458
+ };
33459
+ const { certificationId, ...body } = parsed;
33460
+ if (certificationId !== `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`)
33461
+ throw new TypeError("Mobile release certification content digest is invalid.");
33462
+ return parsed;
33463
+ };
33464
+ var readAbsoluteMobileReleaseCertification = async (projectRoot, requested) => {
33465
+ const selected = resolve18(projectRoot, requested);
33466
+ if (!inside4(projectRoot, selected))
33467
+ throw new TypeError("Mobile release certification must remain inside the project.");
33468
+ const path = basename8(selected) === "certification.json" ? selected : join23(selected, "certification.json");
33469
+ const value = JSON.parse(await readFile21(path, "utf8"));
33470
+ return { certification: parseCertification(value), path };
33471
+ };
33472
+ var renderAbsoluteMobileReleaseCertification = (certification) => {
33473
+ const { release } = certification;
33474
+ const version = release.platform === "ios" ? `${release.marketingVersion ?? "unknown"} (${release.buildNumber ?? "unallocated"})` : String(release.versionCode ?? "unallocated");
33475
+ const evidence = certification.evidence.map((item) => `| ${item.strength} | ${item.networkUnavailable} | ${item.remote ? "paired Mac" : "local host"} | ${item.reportSha256} |`).join(`
33476
+ `);
33477
+ return `# AbsoluteJS mobile release certification
33478
+
33479
+ - Status: CERTIFIED
33480
+ - Certification: ${certification.certificationId}
33481
+ - Generated: ${certification.generatedAt}
33482
+ - Platform: ${release.platform}
33483
+ - Engine: ${release.engine}
33484
+ - Requirement: ${certification.requirement}
33485
+ - Evidence strength: ${certification.strength}
33486
+ - Release: ${release.releaseId}
33487
+ - Artifact SHA-256: ${release.artifactSha256}
33488
+ - Runtime fingerprint: ${release.runtime}
33489
+ - Embedded app build: ${release.appBuild}
33490
+ - Version/build: ${version}
33491
+
33492
+ This certification is local and contains no signing credentials, device identifiers, SSH destinations, report paths, application data, or page contents. Re-run verification against the immutable release before using it as a CI or promotion gate.
33493
+
33494
+ | Strength | Network unavailable | Execution | Report SHA-256 |
33495
+ | --- | --- | --- | --- |
33496
+ ${evidence}
33497
+ `;
33498
+ };
33499
+ var serializeAbsoluteMobileReleaseCertification = (certification) => `${JSON.stringify(certification, null, 2)}
33500
+ `;
33501
+ var verifyAbsoluteMobileReleaseCertification = (certification, release, requirement) => {
33502
+ const parsed = parseCertification(certification);
33503
+ const expected = releaseIdentity(release);
33504
+ if (JSON.stringify(parsed.release) !== JSON.stringify(expected))
33505
+ throw new TypeError("Mobile release certification was invalidated by a release identity, artifact, runtime, signing, version, build, or embedded-bundle change.");
33506
+ if (requirement) {
33507
+ validateRequirement(expected.platform, requirement);
33508
+ if (!satisfiesRequirement(parsed.strength, requirement))
33509
+ throw new TypeError(`Mobile release certification strength ${parsed.strength} does not satisfy required ${requirement} policy.`);
33510
+ }
33511
+ return parsed;
33512
+ };
33513
+ var writeAbsoluteMobileReleaseCertification = async (projectRoot, certification, outputDirectory) => {
33514
+ const root = resolve18(projectRoot);
33515
+ const parent = resolve18(root, outputDirectory ?? join23(".absolutejs", "mobile", "certifications", certification.release.platform, certification.release.releaseId));
33516
+ if (!inside4(root, parent))
33517
+ throw new TypeError("Mobile certification output must remain inside the project.");
33518
+ await mkdir17(parent, { recursive: true });
33519
+ const destination = join23(parent, certification.certificationId);
33520
+ const paths = {
33521
+ directory: destination,
33522
+ jsonPath: join23(destination, "certification.json"),
33523
+ markdownPath: join23(destination, "certification.md")
33524
+ };
33525
+ const json = serializeAbsoluteMobileReleaseCertification(certification);
33526
+ const markdown = renderAbsoluteMobileReleaseCertification(certification);
33527
+ const verifyExisting = async () => {
33528
+ const [existingJson, existingMarkdown] = await Promise.all([
33529
+ readFile21(paths.jsonPath, "utf8"),
33530
+ readFile21(paths.markdownPath, "utf8")
33531
+ ]);
33532
+ if (existingJson !== json || existingMarkdown !== markdown)
33533
+ throw new TypeError(`Immutable mobile certification ${certification.certificationId} already exists with different content.`);
33534
+ };
33535
+ if (await exists8(destination)) {
33536
+ await verifyExisting();
33537
+ return paths;
33538
+ }
33539
+ const staging = await mkdtemp10(join23(parent, ".certification-"));
33540
+ try {
33541
+ await Promise.all([
33542
+ writeFile17(join23(staging, "certification.json"), json, {
33543
+ flag: "wx"
33544
+ }),
33545
+ writeFile17(join23(staging, "certification.md"), markdown, {
33546
+ flag: "wx"
33547
+ })
33548
+ ]);
33549
+ await rename13(staging, destination).catch(async (error) => {
33550
+ if (await exists8(destination))
33551
+ return;
33552
+ throw error;
33553
+ });
33554
+ } finally {
33555
+ await rm13(staging, { force: true, recursive: true });
33556
+ }
33557
+ await verifyExisting();
33558
+ return paths;
33559
+ };
33560
+
33561
+ // src/mobile/ciPromotionAudit.ts
33562
+ var ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT = 1;
33563
+ var ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT = 2;
33564
+ var MAX_GITHUB_OUTPUT_BYTES = 1048576;
33565
+ var MAX_AUDIT_JSON_BYTES = 1048576;
33566
+ var MAX_SEARCH_FILES = 20000;
33567
+ var RUN_ID_PATTERN = /^[1-9][0-9]*$/u;
33568
+ var REPOSITORY_PATTERN2 = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
33569
+ var DISPATCH_ID_PATTERN = /^amp_[a-f0-9]{32}$/u;
33570
+ var PROMOTION_DISCOVERY_ATTEMPTS = 60;
33571
+ var PROMOTION_DISCOVERY_INTERVAL_MS = 1000;
33572
+ var isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
33573
+ var requiredString = (value, name) => {
33574
+ if (typeof value !== "string" || value.trim() === "")
33575
+ throw new TypeError(`${name} is invalid.`);
33576
+ return value.trim();
33577
+ };
33578
+ var optionalString = (value, name) => {
33579
+ if (value === null || value === undefined)
33580
+ return null;
33581
+ return requiredString(value, name);
33582
+ };
33583
+ var runId = (value, name) => {
33584
+ if (!RUN_ID_PATTERN.test(value))
33585
+ throw new TypeError(`${name} is invalid.`);
33586
+ return value;
33587
+ };
33588
+ var dispatchId = (value) => {
33589
+ if (!DISPATCH_ID_PATTERN.test(value))
33590
+ throw new TypeError("Mobile promotion dispatch ID is invalid.");
33591
+ return value;
33592
+ };
33593
+ var promotionRunTitle = (value) => `AbsoluteJS mobile promotion [${dispatchId(value)}]`;
33594
+ var promotionDiscoveryCommand = (workflow, repository) => [
33595
+ "gh",
33596
+ "run",
33597
+ "list",
33598
+ "--workflow",
33599
+ workflow,
33600
+ "--event",
33601
+ "workflow_dispatch",
33602
+ "--limit",
33603
+ "50",
33604
+ ...repositoryArgs(repository),
33605
+ "--json",
33606
+ "databaseId,displayTitle,event,url"
33607
+ ];
33608
+ var repositoryArgs = (repository) => {
33609
+ if (repository === undefined)
33610
+ return [];
33611
+ if (!REPOSITORY_PATTERN2.test(repository))
33612
+ throw new TypeError("GitHub repository must be owner/name.");
33613
+ return ["--repo", repository];
33614
+ };
33615
+ var projectPath = (projectRoot, requested) => {
33616
+ const root = resolve19(projectRoot);
33617
+ const path = resolve19(root, requested);
33618
+ const portable = relative17(root, path);
33619
+ if (portable === ".." || portable.startsWith(`..${sep12}`) || isAbsolute11(portable))
33620
+ throw new TypeError("Mobile CI audit output must remain inside the project.");
33621
+ return path;
33622
+ };
33623
+ var defaultRunner = async (command, options) => {
33624
+ const child = Bun.spawn(command, {
33625
+ cwd: options.cwd,
33626
+ env: options.env,
33627
+ stderr: "pipe",
33628
+ stdin: "ignore",
33629
+ stdout: "pipe"
33630
+ });
33631
+ const [exitCode, stderr, stdout] = await Promise.all([
33632
+ child.exited,
33633
+ new Response(child.stderr).text(),
33634
+ new Response(child.stdout).text()
33635
+ ]);
33636
+ return { exitCode, stderr, stdout };
33637
+ };
33638
+ var requireCommand = async (run, command, options, label) => {
33639
+ const result = await run(command, options);
33640
+ if (result.exitCode !== 0)
33641
+ throw new TypeError(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
33642
+ if (result.stdout.length > MAX_GITHUB_OUTPUT_BYTES || result.stderr.length > MAX_GITHUB_OUTPUT_BYTES)
33643
+ throw new TypeError(`${label} output exceeded the 1 MiB limit.`);
33644
+ return result;
33645
+ };
33646
+ var parseJob = (value) => {
33647
+ if (!isRecord13(value))
33648
+ throw new TypeError("GitHub run job is invalid.");
33649
+ return {
33650
+ conclusion: optionalString(value.conclusion, "GitHub job conclusion"),
33651
+ name: requiredString(value.name, "GitHub job name"),
33652
+ status: requiredString(value.status, "GitHub job status")
33653
+ };
33654
+ };
33655
+ var discoverAbsoluteMobilePromotionRun = async (options) => {
33656
+ const requestedDispatchId = dispatchId(options.dispatchId);
33657
+ const attempts = options.attempts ?? PROMOTION_DISCOVERY_ATTEMPTS;
33658
+ if (!Number.isSafeInteger(attempts) || attempts < 1 || attempts > 300)
33659
+ throw new TypeError("Mobile promotion discovery attempts are invalid.");
33660
+ const pollIntervalMs = options.pollIntervalMs ?? PROMOTION_DISCOVERY_INTERVAL_MS;
33661
+ if (!Number.isSafeInteger(pollIntervalMs) || pollIntervalMs < 0 || pollIntervalMs > 1e4)
33662
+ throw new TypeError("Mobile promotion discovery interval is invalid.");
33663
+ const sleep = options.sleep ?? Bun.sleep;
33664
+ const discover = async (attempt) => {
33665
+ const result = await requireCommand(options.run ?? defaultRunner, promotionDiscoveryCommand(options.workflow, options.repository), { cwd: options.projectRoot }, "GitHub promotion run discovery");
33666
+ const discovered = parseAbsoluteMobilePromotionRunList(JSON.parse(result.stdout), requestedDispatchId);
33667
+ if (discovered)
33668
+ return discovered;
33669
+ if (attempt >= attempts)
33670
+ throw new TypeError(`GitHub did not expose promotion dispatch ${requestedDispatchId} within ${attempts} attempts.`);
33671
+ await sleep(pollIntervalMs);
33672
+ return discover(attempt + 1);
33673
+ };
33674
+ return discover(1);
33675
+ };
33676
+ var inspectAbsoluteMobileCiRun = async (options) => {
33677
+ const requestedRunId = runId(options.runId, "GitHub run ID");
33678
+ const result = await requireCommand(options.run ?? defaultRunner, [
33679
+ "gh",
33680
+ "run",
33681
+ "view",
33682
+ requestedRunId,
33683
+ ...repositoryArgs(options.repository),
33684
+ "--json",
33685
+ "conclusion,createdAt,databaseId,displayTitle,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
33686
+ ], { cwd: options.projectRoot }, "GitHub workflow inspection");
33687
+ return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
33688
+ };
33689
+ var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
33690
+ if (!isRecord13(value) || !Array.isArray(value.jobs))
33691
+ throw new TypeError("GitHub workflow run response is invalid.");
33692
+ const { databaseId } = value;
33693
+ if (typeof databaseId !== "number" && typeof databaseId !== "string" || String(databaseId) !== requestedRunId)
33694
+ throw new TypeError("GitHub workflow run identity does not match.");
33695
+ return {
33696
+ conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
33697
+ createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
33698
+ displayTitle: requiredString(value.displayTitle, "GitHub run displayTitle"),
33699
+ headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
33700
+ headSha: requiredString(value.headSha, "GitHub run headSha"),
33701
+ jobs: value.jobs.map(parseJob),
33702
+ runId: requestedRunId,
33703
+ startedAt: optionalString(value.startedAt, "GitHub run startedAt"),
33704
+ status: requiredString(value.status, "GitHub run status"),
33705
+ updatedAt: requiredString(value.updatedAt, "GitHub run updatedAt"),
33706
+ url: requiredString(value.url, "GitHub run URL"),
33707
+ workflowName: requiredString(value.workflowName, "GitHub workflow name")
33708
+ };
33709
+ };
33710
+ var parseAbsoluteMobilePromotionContext = (value) => {
33711
+ if (!isRecord13(value) || value.format !== 1 && value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
33712
+ throw new TypeError("Mobile promotion context is invalid.");
33713
+ const { platform: platform3 } = value;
33714
+ const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
33715
+ if (sourceArtifact !== `absolute-mobile-${platform3}`)
33716
+ throw new TypeError("Mobile promotion source artifact is invalid.");
33717
+ return {
33718
+ dispatchId: value.format === 1 ? null : dispatchId(requiredString(value.dispatchId, "promotion dispatch ID")),
33719
+ format: value.format,
33720
+ platform: platform3,
33721
+ promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
33722
+ sourceArtifact,
33723
+ sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
33724
+ };
33725
+ };
33726
+ var parseAbsoluteMobilePromotionRunList = (value, requestedDispatchId) => {
33727
+ if (!Array.isArray(value))
33728
+ throw new TypeError("GitHub workflow run list response is invalid.");
33729
+ const expectedTitle = promotionRunTitle(requestedDispatchId);
33730
+ const matches2 = value.filter((candidate) => isRecord13(candidate) && candidate.displayTitle === expectedTitle);
33731
+ if (matches2.length === 0)
33732
+ return null;
33733
+ if (matches2.length !== 1)
33734
+ throw new TypeError("GitHub returned multiple runs for one promotion dispatch identity.");
33735
+ const [match] = matches2;
33736
+ if (!isRecord13(match) || match.event !== "workflow_dispatch")
33737
+ throw new TypeError("GitHub promotion run identity is invalid.");
33738
+ const databaseId = typeof match.databaseId === "number" || typeof match.databaseId === "string" ? String(match.databaseId) : "";
33739
+ const requestedRunId = runId(databaseId, "GitHub promotion run ID");
33740
+ const url = requiredString(match.url, "GitHub promotion run URL");
33741
+ if (!url.endsWith(`/actions/runs/${requestedRunId}`))
33742
+ throw new TypeError("GitHub promotion run URL does not match its ID.");
33743
+ return {
33744
+ dispatchId: requestedDispatchId,
33745
+ runId: requestedRunId,
33746
+ url
33747
+ };
33748
+ };
33749
+ var watchAbsoluteMobileCiRun = async (options) => {
33750
+ const requestedRunId = runId(options.runId, "GitHub run ID");
33751
+ await requireCommand(options.run ?? defaultRunner, [
33752
+ "gh",
33753
+ "run",
33754
+ "watch",
33755
+ requestedRunId,
33756
+ ...repositoryArgs(options.repository),
33757
+ "--exit-status"
33758
+ ], { cwd: options.projectRoot }, "GitHub workflow watch");
33759
+ return inspectAbsoluteMobileCiRun({ ...options, runId: requestedRunId });
33760
+ };
33761
+ var readBoundedJson = async (path, label) => {
33762
+ const metadata = await stat7(path).catch(() => null);
33763
+ if (!metadata?.isFile() || metadata.size > MAX_AUDIT_JSON_BYTES)
33764
+ throw new TypeError(`${label} is missing or exceeds the 1 MiB limit.`);
33765
+ const value = JSON.parse(await readFile22(path, "utf8"));
33766
+ return value;
33767
+ };
33768
+ var findFiles = async (root, name) => {
33769
+ const found = [];
33770
+ let visited = 0;
33771
+ const walk = async (directory) => {
33772
+ const entries = await readdir7(directory, { withFileTypes: true });
33773
+ await Promise.all(entries.map(async (entry) => {
33774
+ visited += 1;
33775
+ if (visited > MAX_SEARCH_FILES)
33776
+ throw new TypeError("Downloaded GitHub artifact contains too many files.");
33777
+ const path = join24(directory, entry.name);
33778
+ if (entry.isDirectory()) {
33779
+ await walk(path);
33780
+ return;
33781
+ }
33782
+ if (entry.isFile() && entry.name === name)
33783
+ found.push(path);
33784
+ }));
33785
+ };
33786
+ await walk(root);
33787
+ return found;
33788
+ };
33789
+ var inferPromotionPlatform = (status) => {
33790
+ const matched = status.jobs.flatMap((job) => {
33791
+ if (job.name === "Promote exact certified Android release")
33792
+ return ["android"];
33793
+ if (job.name === "Promote exact certified iOS release")
33794
+ return ["ios"];
33795
+ return [];
33796
+ });
33797
+ if (matched.length !== 1)
33798
+ throw new TypeError("GitHub run does not contain exactly one mobile promotion job.");
33799
+ const [platform3] = matched;
33800
+ if (!platform3)
33801
+ throw new TypeError("Mobile promotion platform is missing.");
33802
+ return platform3;
33803
+ };
33804
+ var onlyPath = (paths, label) => {
33805
+ const [path] = paths;
33806
+ if (paths.length !== 1 || !path)
33807
+ throw new TypeError(`${label} must contain exactly one matching file.`);
33808
+ return path;
33809
+ };
33810
+ var canonicalJson2 = (value) => {
33811
+ if (Array.isArray(value))
33812
+ return `[${value.map((entry) => canonicalJson2(entry)).join(",")}]`;
33813
+ if (isRecord13(value))
33814
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
33815
+ return JSON.stringify(value);
33816
+ };
33817
+ var publicationSummary = (value, platform3, release, certification) => {
33818
+ if (!isRecord13(value) || !isRecord13(value.record) || !isRecord13(value.record.metadata))
33819
+ throw new TypeError("Mobile promotion receipt is invalid.");
33820
+ const { metadata } = value.record;
33821
+ if (metadata.appId !== release.metadata.appId || metadata.releaseId !== release.metadata.releaseId || metadata.sha256 !== release.metadata.sha256)
33822
+ throw new TypeError("Mobile promotion receipt release identity does not match.");
33823
+ if (!isRecord13(value.certification) || !isRecord13(value.certification.provenance))
33824
+ throw new TypeError("Mobile promotion receipt lacks trusted certification provenance.");
33825
+ 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)
33826
+ throw new TypeError("Mobile promotion certification receipt does not match.");
33827
+ requiredString(value.certification.provenance.issuer, "promotion provenance issuer");
33828
+ requiredString(value.certification.provenance.verificationId, "promotion provenance verification ID");
33829
+ const provenanceTime = requiredString(value.certification.provenance.verifiedAt, "promotion provenance verification time");
33830
+ if (Number.isNaN(Date.parse(provenanceTime)))
33831
+ throw new TypeError("Mobile promotion provenance time is invalid.");
33832
+ if (typeof value.reused !== "boolean")
33833
+ throw new TypeError("Mobile promotion reuse status is invalid.");
33834
+ const channel = isRecord13(value.channel) ? optionalString(value.channel.channel, "promotion channel") : null;
33835
+ if (isRecord13(value.channel) && value.channel.releaseId !== release.metadata.releaseId)
33836
+ throw new TypeError("Mobile promotion channel receipt does not match.");
33837
+ const providerReceipt = platform3 === "android" ? value.googlePlay : value.appStoreConnect;
33838
+ if (providerReceipt === undefined)
33839
+ return {
33840
+ channel,
33841
+ provider: "registry",
33842
+ reused: value.reused,
33843
+ stage: null
33844
+ };
33845
+ if (!isRecord13(providerReceipt) || !isRecord13(providerReceipt.receipt))
33846
+ throw new TypeError("Mobile store promotion receipt is invalid.");
33847
+ if (typeof providerReceipt.reused !== "boolean")
33848
+ throw new TypeError("Mobile store promotion reuse status is invalid.");
33849
+ const expectedProvider = platform3 === "android" ? "google-play" : "app-store-connect";
33850
+ if (providerReceipt.receipt.provider !== expectedProvider || providerReceipt.receipt.releaseId !== release.metadata.releaseId || providerReceipt.receipt.sha256 !== release.metadata.sha256)
33851
+ throw new TypeError("Mobile store promotion receipt does not match.");
33852
+ return {
33853
+ channel,
33854
+ provider: expectedProvider,
33855
+ reused: providerReceipt.reused,
33856
+ stage: requiredString(providerReceipt.receipt.stage, "Mobile store promotion stage")
33857
+ };
33858
+ };
33859
+ var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
33860
+
33861
+ - Status: **VERIFIED**
33862
+ - Platform: ${audit.platform}
33863
+ - Release: \`${audit.release.releaseId}\`
33864
+ - Artifact SHA-256: \`${audit.release.sha256}\`
33865
+ - Source workflow run: ${audit.github.sourceRunId}
33866
+ - Promotion workflow run: ${audit.github.promotionRunId}
33867
+ - Dispatch correlation: ${audit.github.dispatchId ?? "legacy workflow (not recorded)"}
33868
+ - Workflow: ${audit.github.workflowName}
33869
+ - GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
33870
+ - Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
33871
+ - Publication: ${audit.publication.provider}${audit.publication.stage ? ` / ${audit.publication.stage}` : ""}
33872
+ - Verified at: ${audit.verifiedAt}
33873
+
33874
+ This report contains no credentials, device identifiers, local absolute paths, application data, or GitHub token.
33875
+ `;
33876
+ var auditAbsoluteMobileCiPromotion = async (options) => {
33877
+ const runner = options.run ?? defaultRunner;
33878
+ const promotionRunId = runId(options.runId, "GitHub promotion run ID");
33879
+ const status = await inspectAbsoluteMobileCiRun({
33880
+ projectRoot: options.projectRoot,
33881
+ ...options.repository ? { repository: options.repository } : {},
33882
+ run: runner,
33883
+ runId: promotionRunId
33884
+ });
33885
+ if (status.status !== "completed" || status.conclusion !== "success")
33886
+ throw new TypeError("Mobile promotion run has not completed successfully.");
33887
+ const platform3 = inferPromotionPlatform(status);
33888
+ const output = projectPath(options.projectRoot, options.outputDirectory ?? `.absolutejs/mobile-ci/audits/${promotionRunId}`);
33889
+ if (await access18(output).then(() => true).catch(() => false))
33890
+ throw new TypeError(`Mobile CI audit output already exists: ${output}`);
33891
+ await mkdir18(dirname16(output), { recursive: true });
33892
+ const staging = await mkdtemp11(join24(dirname16(output), ".promotion-audit-"));
33893
+ try {
33894
+ const evidenceRoot = join24(staging, "promotion-evidence");
33895
+ await mkdir18(evidenceRoot);
33896
+ await requireCommand(runner, [
33897
+ "gh",
33898
+ "run",
33899
+ "download",
33900
+ promotionRunId,
33901
+ ...repositoryArgs(options.repository),
33902
+ "--name",
33903
+ `absolute-mobile-${platform3}-promotion-${promotionRunId}`,
33904
+ "--dir",
33905
+ evidenceRoot
33906
+ ], { cwd: options.projectRoot }, "GitHub promotion artifact download");
33907
+ const contextPaths = await findFiles(evidenceRoot, "promotion-context.json");
33908
+ let context;
33909
+ if (contextPaths.length === 1)
33910
+ context = parseAbsoluteMobilePromotionContext(await readBoundedJson(onlyPath(contextPaths, "Promotion context"), "Promotion context"));
33911
+ else if (contextPaths.length > 1)
33912
+ throw new TypeError("Promotion artifact contains duplicate contexts.");
33913
+ if (context && context.promotionRunId !== promotionRunId)
33914
+ throw new TypeError("Promotion context run identity does not match.");
33915
+ if (context && context.platform !== platform3)
33916
+ throw new TypeError("Promotion context platform does not match.");
33917
+ if (context?.dispatchId && status.displayTitle !== promotionRunTitle(context.dispatchId))
33918
+ throw new TypeError("Promotion dispatch identity does not match the GitHub run.");
33919
+ const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
33920
+ if (!sourceRunId)
33921
+ throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
33922
+ if (context && options.sourceRunId && context.sourceRunId !== sourceRunId)
33923
+ throw new TypeError("Requested source run does not match promotion context.");
33924
+ const sourceArtifact = context?.sourceArtifact ?? `absolute-mobile-${platform3}`;
33925
+ const sourceRoot = join24(staging, "source-release");
33926
+ await mkdir18(sourceRoot);
33927
+ await requireCommand(runner, [
33928
+ "gh",
33929
+ "run",
33930
+ "download",
33931
+ sourceRunId,
33932
+ ...repositoryArgs(options.repository),
33933
+ "--name",
33934
+ sourceArtifact,
33935
+ "--dir",
33936
+ sourceRoot
33937
+ ], { cwd: options.projectRoot }, "GitHub source release download");
33938
+ const releaseMetadata = await findFiles(sourceRoot, "release.json");
33939
+ if (releaseMetadata.length !== 1)
33940
+ throw new TypeError("Source artifact must contain exactly one immutable mobile release.");
33941
+ const releaseMetadataPath = onlyPath(releaseMetadata, "Source artifact");
33942
+ const release = platform3 === "android" ? await readAbsoluteAndroidRelease(options.projectRoot, releaseMetadataPath) : await readAbsoluteIosRelease(options.projectRoot, releaseMetadataPath);
33943
+ const certificationPaths = await findFiles(evidenceRoot, "certification.json");
33944
+ const verificationPaths = await findFiles(evidenceRoot, "verification.json");
33945
+ const receiptPaths = await findFiles(evidenceRoot, "promotion-receipt.json");
33946
+ if (certificationPaths.length !== 1 || verificationPaths.length !== 1 || receiptPaths.length !== 1)
33947
+ throw new TypeError("Promotion audit artifact is incomplete or ambiguous.");
33948
+ const certificationPath = onlyPath(certificationPaths, "Promotion certification");
33949
+ const verificationPath = onlyPath(verificationPaths, "Promotion verification");
33950
+ const receiptPath = onlyPath(receiptPaths, "Promotion receipt");
33951
+ const loadedCertification = await readAbsoluteMobileReleaseCertification(options.projectRoot, certificationPath);
33952
+ const certification = verifyAbsoluteMobileReleaseCertification(loadedCertification.certification, release);
33953
+ const verification = await readAbsoluteMobileCertificationVerification(options.projectRoot, verificationPath);
33954
+ if (verification.identity.sha !== status.headSha)
33955
+ throw new TypeError("Sigstore workflow commit does not match the promotion run.");
33956
+ if (options.repository && verification.identity.repository !== options.repository)
33957
+ throw new TypeError("Sigstore repository does not match the requested repository.");
33958
+ const bundlePath = `${certificationPath}.sigstore.json`;
33959
+ const bundle = await readAbsoluteSigstoreBundle(options.projectRoot, bundlePath);
33960
+ if (canonicalJson2(bundle) !== canonicalJson2(verification.bundle))
33961
+ throw new TypeError("Sigstore bundle does not match verification envelope.");
33962
+ await requireCommand(runner, [
33963
+ "bunx",
33964
+ "@absolutejs/attest@0.2.0",
33965
+ "verify-blobs",
33966
+ certificationPath
33967
+ ], {
33968
+ cwd: options.projectRoot,
33969
+ env: {
33970
+ ...process.env,
33971
+ GITHUB_REF: verification.identity.ref,
33972
+ GITHUB_REPOSITORY: verification.identity.repository,
33973
+ GITHUB_SHA: verification.identity.sha,
33974
+ GITHUB_WORKFLOW_REF: `${verification.identity.repository}/${verification.identity.workflowPath}@${verification.identity.ref}`
33975
+ }
33976
+ }, "Sigstore certification verification");
33977
+ const publication = publicationSummary(await readBoundedJson(receiptPath, "Mobile promotion receipt"), platform3, release, certification);
33978
+ const audit = {
33979
+ certification: {
33980
+ certificationId: certification.certificationId,
33981
+ requirement: certification.requirement,
33982
+ strength: certification.strength
33983
+ },
33984
+ format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
33985
+ github: {
33986
+ conclusion: status.conclusion,
33987
+ dispatchId: context?.dispatchId ?? null,
33988
+ promotionRunId,
33989
+ repository: verification.identity.repository,
33990
+ sourceRunId,
33991
+ url: status.url,
33992
+ workflowName: status.workflowName
33993
+ },
33994
+ platform: platform3,
33995
+ provenance: verification.identity,
33996
+ publication,
33997
+ release: {
33998
+ appId: release.metadata.appId,
33999
+ bytes: release.metadata.bytes,
34000
+ engine: release.metadata.engine,
34001
+ releaseId: release.metadata.releaseId,
34002
+ runtime: release.metadata.runtime,
34003
+ sha256: release.metadata.sha256
34004
+ },
34005
+ status: "verified",
34006
+ verifiedAt: new Date().toISOString()
34007
+ };
34008
+ await Promise.all([
34009
+ writeFile18(join24(staging, "audit.json"), `${JSON.stringify(audit, null, 2)}
34010
+ `, { mode: 384 }),
34011
+ writeFile18(join24(staging, "audit.md"), renderAuditMarkdown(audit), {
34012
+ mode: 384
34013
+ })
34014
+ ]);
34015
+ await rename14(staging, output);
34016
+ return { audit, directory: output };
34017
+ } catch (error) {
34018
+ await rm14(staging, { force: true, recursive: true });
34019
+ throw error;
34020
+ }
34021
+ };
34022
+ // src/mobile/ciWorkflow.ts
34023
+ import { existsSync as existsSync4 } from "fs";
34024
+ import { access as access19, mkdir as mkdir19, readFile as readFile23, writeFile as writeFile19 } from "fs/promises";
34025
+ import { dirname as dirname17, extname as extname5, relative as relative18, resolve as resolve20, sep as sep13 } from "path";
34026
+ var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 4;
34027
+ var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
34028
+ var CI_ENV_INDENTATION = 6;
34029
+ var RESERVED_SECRET_NAMES = new Set([
34030
+ "ABSOLUTE_ANDROID_KEYSTORE_BASE64",
34031
+ "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
34032
+ "ABSOLUTE_ANDROID_KEY_ALIAS",
34033
+ "ABSOLUTE_ANDROID_KEY_PASSWORD",
34034
+ "ABSOLUTE_GOOGLE_CREDENTIALS_BASE64",
34035
+ "ABSOLUTE_IOS_CERTIFICATE_BASE64",
34036
+ "ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
34037
+ "ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
34038
+ "ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
34039
+ "ABSOLUTE_IOS_DEVELOPMENT_TEAM",
34040
+ "APP_STORE_CONNECT_ISSUER_ID",
34041
+ "APP_STORE_CONNECT_KEY_ID",
34042
+ "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
34043
+ ]);
34044
+ var exists9 = async (path) => {
34045
+ try {
34046
+ await access19(path);
34047
+ return true;
34048
+ } catch {
34049
+ return false;
34050
+ }
34051
+ };
34052
+ var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
34053
+ var projectPath2 = (projectRoot, value, field2, options = {}) => {
34054
+ const root = resolve20(projectRoot);
34055
+ const path = resolve20(root, value);
34056
+ const portable = relative18(root, path).replaceAll("\\", "/");
34057
+ if (portable === ".." || portable.startsWith(`..${sep13}`) || portable.startsWith("../") || portable === "") {
34058
+ throw new TypeError(`${field2} must remain inside the project root.`);
34059
+ }
34060
+ if (/\r|\n/u.test(portable) || portable.startsWith("-"))
34061
+ throw new TypeError(`${field2} contains an unsafe path.`);
34062
+ if (!options.allowMissing && !existsSync4(path))
34063
+ throw new TypeError(`${field2} does not exist inside the project.`);
34064
+ return portable;
34065
+ };
34066
+ var workflowOutputPath = (projectRoot, value) => {
34067
+ const root = resolve20(projectRoot);
34068
+ const workflows = resolve20(root, ".github/workflows");
34069
+ const path = resolve20(root, value ?? ".github/workflows/absolute-mobile.yml");
34070
+ const portable = relative18(workflows, path);
34071
+ if (portable === ".." || portable.startsWith(`..${sep13}`) || extname5(path) !== ".yml" && extname5(path) !== ".yaml") {
34072
+ throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
34073
+ }
34074
+ return path;
34075
+ };
34076
+ var normalizeSecretEnvironment = (values = []) => {
34077
+ const names = [...new Set(values)].sort();
34078
+ for (const name of names) {
34079
+ if (!SECRET_NAME_PATTERN.test(name))
34080
+ throw new TypeError("mobile ci github --secret-env values must be uppercase environment variable names.");
34081
+ if (name.startsWith("GITHUB_") || name.startsWith("RUNNER_") || name.startsWith("ACTIONS_") || RESERVED_SECRET_NAMES.has(name)) {
34082
+ throw new TypeError(`mobile ci github --secret-env cannot replace reserved variable ${name}.`);
34083
+ }
34084
+ }
34085
+ return names;
34086
+ };
34087
+ var customSecretEnvironment = (names, indentation = CI_ENV_INDENTATION) => names.map((name) => `${" ".repeat(indentation)}${name}: \${{ secrets.${name} }}`).join(`
34088
+ `);
34089
+ var commandEnvironment2 = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
34090
+ ABSOLUTE_REGISTRY_MODULE: ${yamlString(options.registryModule)}
34091
+ ABSOLUTE_SERVER_ENTRY: ${yamlString(options.serverEntry)}`;
34092
+ var appendConfigArgument = `if [[ -n "$ABSOLUTE_CONFIG_PATH" ]]; then
34093
+ args+=(--config "$ABSOLUTE_CONFIG_PATH")
34094
+ fi`;
34095
+ var installSteps = ` - name: Check out source
34096
+ uses: actions/checkout@v6
34097
+ - name: Install Bun
34098
+ uses: oven-sh/setup-bun@v2
34099
+ - name: Install exact dependencies
34100
+ run: bun ci`;
34101
+ var bundleAuditSteps = ` - name: Prepare production mobile bundle
34102
+ shell: bash
34103
+ run: |
34104
+ args=(bunx absolute prepare "$ABSOLUTE_SERVER_ENTRY" --outdir .absolutejs/mobile-ci/server)
34105
+ ${appendConfigArgument}
34106
+ "\${args[@]}"
34107
+ - name: Run cryptographic mobile bundle audit
34108
+ id: mobile-bundle-audit
34109
+ continue-on-error: true
34110
+ shell: bash
34111
+ run: |
34112
+ mkdir -p .absolutejs/mobile-ci
34113
+ args=(bunx absolute mobile inspect --json --require-bundle)
34114
+ ${appendConfigArgument}
34115
+ "\${args[@]}" > .absolutejs/mobile-ci/inspection.json
34116
+ - name: Upload mobile inspection report
34117
+ if: always()
34118
+ uses: actions/upload-artifact@v7
34119
+ with:
34120
+ name: absolute-mobile-inspection
34121
+ path: .absolutejs/mobile-ci/inspection.json
34122
+ if-no-files-found: error
34123
+ retention-days: 30
34124
+ include-hidden-files: true
34125
+ - name: Enforce mobile bundle audit
34126
+ if: steps.mobile-bundle-audit.outcome != 'success'
34127
+ run: exit 1`;
34128
+ var releaseAuditSteps = (platform3) => ` - name: Run redacted mobile release audit
34129
+ id: mobile-release-audit
34130
+ continue-on-error: true
34131
+ shell: bash
34132
+ run: |
34133
+ mkdir -p .absolutejs/mobile-ci
34134
+ args=(bunx absolute mobile doctor release ${platform3} --json)
34135
+ ${appendConfigArgument}
34136
+ "\${args[@]}" > .absolutejs/mobile-ci/compliance.json
34137
+ - name: Upload mobile compliance report
34138
+ if: always()
34139
+ uses: actions/upload-artifact@v7
34140
+ with:
34141
+ name: absolute-mobile-compliance-\${{ github.job }}
34142
+ path: .absolutejs/mobile-ci/compliance.json
34143
+ if-no-files-found: error
34144
+ retention-days: 30
34145
+ include-hidden-files: true
34146
+ - name: Enforce mobile release audit
34147
+ if: steps.mobile-release-audit.outcome != 'success'
34148
+ run: exit 1`;
34149
+ var platformInput = (platforms) => {
34150
+ const choices = platforms.length === 2 ? ["all", ...platforms] : platforms;
34151
+ return ` platform:
34152
+ description: Native platform to build
34153
+ required: true
34154
+ type: choice
34155
+ default: ${choices[0]}
33166
34156
  options:
33167
34157
  ${choices.map((value) => ` - ${value}`).join(`
33168
34158
  `)}`;
@@ -33192,6 +34182,10 @@ var publishingInputs = (platforms, includePublishing) => {
33192
34182
  description: Source workflow run ID for promotion without rebuilding
33193
34183
  required: false
33194
34184
  type: string`,
34185
+ ` dispatch_id:
34186
+ description: Unique promotion correlation ID supplied by the AbsoluteJS CLI
34187
+ required: false
34188
+ type: string`,
33195
34189
  ` certification_base64:
33196
34190
  description: AbsoluteJS certification supplied by the promotion CLI
33197
34191
  required: false
@@ -33564,6 +34558,10 @@ var promotionValidationSteps = ` - name: Validate promotion handoff
33564
34558
  echo "Promotion requires a numeric source workflow run ID." >&2
33565
34559
  exit 1
33566
34560
  fi
34561
+ if [[ ! "$ABSOLUTE_PROMOTION_DISPATCH_ID" =~ ^amp_[a-f0-9]{32}$ ]]; then
34562
+ echo "Promotion requires a valid AbsoluteJS dispatch correlation ID." >&2
34563
+ exit 1
34564
+ fi
33567
34565
  if [[ -z "$ABSOLUTE_CERTIFICATION_BASE64" ]]; then
33568
34566
  echo "Promotion requires certification supplied by absolute mobile ci promote." >&2
33569
34567
  exit 1
@@ -33575,6 +34573,10 @@ var promotionSharedSteps = (platform3) => ` - name: Restore exact ${platfor
33575
34573
  path: .absolutejs/mobile/releases/${platform3}
33576
34574
  run-id: \${{ inputs.source_run_id }}
33577
34575
  github-token: \${{ github.token }}
34576
+ - name: Record bounded promotion context
34577
+ shell: bash
34578
+ run: |
34579
+ 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(platform3)}, promotionRunId, sourceArtifact: ${JSON.stringify(`absolute-mobile-${platform3}`)}, sourceRunId }, null, 2) + "\\n");'
33578
34580
  - name: Import and verify exact certification
33579
34581
  shell: bash
33580
34582
  run: |
@@ -33612,7 +34614,9 @@ var androidPromotionJob = (options) => {
33612
34614
  id-token: write
33613
34615
  env:
33614
34616
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
34617
+ ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
33615
34618
  ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
34619
+ ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
33616
34620
  ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
33617
34621
  ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
33618
34622
  GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json${custom ? `
@@ -33652,6 +34656,7 @@ ${promotionSharedSteps("android")}
33652
34656
  name: absolute-mobile-android-promotion-\${{ github.run_id }}
33653
34657
  path: |
33654
34658
  .absolutejs/mobile-ci/imported-certification/
34659
+ .absolutejs/mobile-ci/promotion-context.json
33655
34660
  .absolutejs/mobile-ci/promotion-receipt.json
33656
34661
  if-no-files-found: error
33657
34662
  retention-days: 90
@@ -33675,7 +34680,9 @@ var iosPromotionJob = (options) => {
33675
34680
  id-token: write
33676
34681
  env:
33677
34682
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
34683
+ ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
33678
34684
  ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
34685
+ ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
33679
34686
  ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
33680
34687
  ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
33681
34688
  APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
@@ -33717,6 +34724,7 @@ ${promotionSharedSteps("ios")}
33717
34724
  name: absolute-mobile-ios-promotion-\${{ github.run_id }}
33718
34725
  path: |
33719
34726
  .absolutejs/mobile-ci/imported-certification/
34727
+ .absolutejs/mobile-ci/promotion-context.json
33720
34728
  .absolutejs/mobile-ci/promotion-receipt.json
33721
34729
  if-no-files-found: error
33722
34730
  retention-days: 90
@@ -33753,9 +34761,9 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
33753
34761
  ].sort();
33754
34762
  const includePublishing = options.includePublishing === true;
33755
34763
  const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
33756
- const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
33757
- const configPath = options.configPath ? projectPath(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
33758
- const registryModule = projectPath(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
34764
+ const serverEntry = projectPath2(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
34765
+ const configPath = options.configPath ? projectPath2(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
34766
+ const registryModule = projectPath2(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
33759
34767
  const environment = commandEnvironment2({
33760
34768
  configPath,
33761
34769
  registryModule,
@@ -33763,6 +34771,7 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
33763
34771
  });
33764
34772
  let workflow = `# Generated by AbsoluteJS. Regenerate with: absolute mobile ci github${includePublishing ? " --publish" : ""}
33765
34773
  name: AbsoluteJS Mobile
34774
+ run-name: \${{ inputs.operation == 'promote' && format('AbsoluteJS mobile promotion [{0}]', inputs.dispatch_id) || format('AbsoluteJS mobile {0}', github.event_name) }}
33766
34775
 
33767
34776
  on:
33768
34777
  pull_request:
@@ -33788,6 +34797,7 @@ jobs:
33788
34797
  env:
33789
34798
  ${environment}${includePublishing ? `
33790
34799
  ABSOLUTE_PROMOTION_PLATFORM: \${{ inputs.platform }}
34800
+ ABSOLUTE_PROMOTION_DISPATCH_ID: \${{ inputs.dispatch_id }}
33791
34801
  ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
33792
34802
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}` : ""}
33793
34803
  steps:
@@ -33819,12 +34829,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
33819
34829
  var writeAbsoluteMobileGithubWorkflow = async (options) => {
33820
34830
  const path = workflowOutputPath(options.projectRoot, options.outputPath);
33821
34831
  const generated = createAbsoluteMobileGithubWorkflow(options);
33822
- const previous = await exists8(path) ? await readFile20(path, "utf8") : undefined;
34832
+ const previous = await exists9(path) ? await readFile23(path, "utf8") : undefined;
33823
34833
  if (previous !== undefined && previous !== generated.workflow && !options.force)
33824
- throw new TypeError(`${relative15(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
34834
+ throw new TypeError(`${relative18(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
33825
34835
  if (previous !== generated.workflow) {
33826
- await mkdir16(dirname15(path), { recursive: true });
33827
- await writeFile16(path, generated.workflow);
34836
+ await mkdir19(dirname17(path), { recursive: true });
34837
+ await writeFile19(path, generated.workflow);
33828
34838
  }
33829
34839
  return {
33830
34840
  changed: previous !== generated.workflow,
@@ -33842,20 +34852,20 @@ init_materializedBundle();
33842
34852
  init_mobilePreview();
33843
34853
 
33844
34854
  // src/mobile/nativeDeepLinks.ts
33845
- import { readFile as readFile21, rename as rename13, writeFile as writeFile17 } from "fs/promises";
33846
- import { join as join25 } from "path";
34855
+ import { readFile as readFile24, rename as rename15, writeFile as writeFile20 } from "fs/promises";
34856
+ import { join as join27 } from "path";
33847
34857
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
33848
34858
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
33849
34859
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
33850
34860
  var NOT_FOUND = -1;
33851
34861
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
33852
34862
  var writeChangedFile = async (path, source) => {
33853
- const current = await readFile21(path, "utf8");
34863
+ const current = await readFile24(path, "utf8");
33854
34864
  if (current === source)
33855
34865
  return false;
33856
34866
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
33857
- await writeFile17(temporary, source, { flag: "wx" });
33858
- await rename13(temporary, path);
34867
+ await writeFile20(temporary, source, { flag: "wx" });
34868
+ await rename15(temporary, path);
33859
34869
  return true;
33860
34870
  };
33861
34871
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -33900,8 +34910,8 @@ ${hosts}
33900
34910
  `;
33901
34911
  };
33902
34912
  var configureAndroid = async (config) => {
33903
- const path = join25(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
33904
- const source = await readFile21(path, "utf8");
34913
+ const path = join27(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
34914
+ const source = await readFile24(path, "utf8");
33905
34915
  const mainActivity = source.indexOf('android:name=".MainActivity"');
33906
34916
  if (mainActivity === NOT_FOUND) {
33907
34917
  throw new TypeError("Android MainActivity was not found.");
@@ -33926,8 +34936,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
33926
34936
  ${END_MARKER}
33927
34937
  `;
33928
34938
  var configureIosInfo = async (config) => {
33929
- const path = join25(config.nativeProjectDirectory, "ios/App/App/Info.plist");
33930
- const source = await readFile21(path, "utf8");
34939
+ const path = join27(config.nativeProjectDirectory, "ios/App/App/Info.plist");
34940
+ const source = await readFile24(path, "utf8");
33931
34941
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
33932
34942
  ${END_MARKER}
33933
34943
  `;
@@ -33950,10 +34960,10 @@ ${domains}
33950
34960
  `;
33951
34961
  };
33952
34962
  var configureIosEntitlements = async (config) => {
33953
- const path = join25(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
34963
+ const path = join27(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
33954
34964
  let current = "";
33955
34965
  try {
33956
- current = await readFile21(path, "utf8");
34966
+ current = await readFile24(path, "utf8");
33957
34967
  } catch (error) {
33958
34968
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
33959
34969
  throw error;
@@ -33963,13 +34973,13 @@ var configureIosEntitlements = async (config) => {
33963
34973
  if (current === source)
33964
34974
  return false;
33965
34975
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
33966
- await writeFile17(temporary, source, { flag: "wx" });
33967
- await rename13(temporary, path);
34976
+ await writeFile20(temporary, source, { flag: "wx" });
34977
+ await rename15(temporary, path);
33968
34978
  return true;
33969
34979
  };
33970
34980
  var configureIosProject = async (config) => {
33971
- const path = join25(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
33972
- const source = await readFile21(path, "utf8");
34981
+ const path = join27(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
34982
+ const source = await readFile24(path, "utf8");
33973
34983
  const declarations = [
33974
34984
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
33975
34985
  ].map((match) => match[1]);
@@ -34008,8 +35018,8 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
34008
35018
  };
34009
35019
  // src/mobile/nativeDeviceCapabilities.ts
34010
35020
  init_deviceCapabilities();
34011
- import { readFile as readFile22, rename as rename14, writeFile as writeFile18 } from "fs/promises";
34012
- import { join as join26 } from "path";
35021
+ import { readFile as readFile25, rename as rename16, writeFile as writeFile21 } from "fs/promises";
35022
+ import { join as join28 } from "path";
34013
35023
  var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
34014
35024
  var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
34015
35025
  var NOT_FOUND2 = -1;
@@ -34019,12 +35029,12 @@ var PUSH_START_MARKER = "absolutejs:push-notifications:start";
34019
35029
  var PUSH_END_MARKER = "absolutejs:push-notifications:end";
34020
35030
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
34021
35031
  var writeChangedFile2 = async (path, source) => {
34022
- const current = await readFile22(path, "utf8");
35032
+ const current = await readFile25(path, "utf8");
34023
35033
  if (current === source)
34024
35034
  return false;
34025
35035
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
34026
- await writeFile18(temporary, source, { flag: "wx" });
34027
- await rename14(temporary, path);
35036
+ await writeFile21(temporary, source, { flag: "wx" });
35037
+ await rename16(temporary, path);
34028
35038
  return true;
34029
35039
  };
34030
35040
  var writeOptionalChangedFile = async (path, source) => {
@@ -34032,14 +35042,14 @@ var writeOptionalChangedFile = async (path, source) => {
34032
35042
  if (current === source)
34033
35043
  return false;
34034
35044
  if (current === null) {
34035
- await writeFile18(path, source, { flag: "wx" });
35045
+ await writeFile21(path, source, { flag: "wx" });
34036
35046
  return true;
34037
35047
  }
34038
35048
  return writeChangedFile2(path, source);
34039
35049
  };
34040
35050
  var optionalSource = async (path) => {
34041
35051
  try {
34042
- return await readFile22(path, "utf8");
35052
+ return await readFile25(path, "utf8");
34043
35053
  } catch (error) {
34044
35054
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
34045
35055
  return null;
@@ -34153,15 +35163,15 @@ var writeIosPrivacyManifest = async (path, current, source) => {
34153
35163
  return false;
34154
35164
  if (current !== null)
34155
35165
  return writeChangedFile2(path, source);
34156
- await writeFile18(path, source, { flag: "wx" });
35166
+ await writeFile21(path, source, { flag: "wx" });
34157
35167
  return true;
34158
35168
  };
34159
35169
  var configureIosPrivacyProject = async (config, requirements) => {
34160
35170
  if (requirements.iosPrivacyAccessedApis.length === 0)
34161
35171
  return false;
34162
- const projectPath2 = join26(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
34163
- const project = await readFile22(projectPath2, "utf8");
34164
- return writeChangedFile2(projectPath2, addIosPrivacyProjectReference(project));
35172
+ const projectPath3 = join28(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
35173
+ const project = await readFile25(projectPath3, "utf8");
35174
+ return writeChangedFile2(projectPath3, addIosPrivacyProjectReference(project));
34165
35175
  };
34166
35176
  var addIosPrivacyProjectReference = (source) => {
34167
35177
  const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
@@ -34212,8 +35222,8 @@ ${next.slice(index)}`;
34212
35222
  return next;
34213
35223
  };
34214
35224
  var configureIos2 = async (config, plan) => {
34215
- const path = join26(config.nativeProjectDirectory, "ios/App/App/Info.plist");
34216
- const source = await readFile22(path, "utf8");
35225
+ const path = join28(config.nativeProjectDirectory, "ios/App/App/Info.plist");
35226
+ const source = await readFile25(path, "utf8");
34217
35227
  const requirements = absoluteDeviceNativeRequirements(plan);
34218
35228
  const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
34219
35229
  const ownedStart = source.indexOf(START_MARKER2);
@@ -34233,7 +35243,7 @@ ${content}
34233
35243
  ${END_MARKER2}
34234
35244
  ` : "";
34235
35245
  const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
34236
- const privacyPath = join26(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
35246
+ const privacyPath = join28(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
34237
35247
  const privacyCurrent = await optionalSource(privacyPath);
34238
35248
  const privacySource = privacyManifestSource(privacyCurrent, requirements);
34239
35249
  const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
@@ -34244,7 +35254,7 @@ ${content}
34244
35254
  return infoChanged || privacyChanged || projectChanged || pushChanged;
34245
35255
  };
34246
35256
  var configureIosPushNotifications = async (config, enabled) => {
34247
- const entitlementsPath = join26(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
35257
+ const entitlementsPath = join28(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
34248
35258
  const entitlements = await optionalSource(entitlementsPath);
34249
35259
  if (entitlements === null && !enabled)
34250
35260
  return false;
@@ -34256,7 +35266,7 @@ var configureIosPushNotifications = async (config, enabled) => {
34256
35266
  <!-- ${PUSH_END_MARKER} -->
34257
35267
  ` : "";
34258
35268
  const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
34259
- const delegatePath = join26(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
35269
+ const delegatePath = join28(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
34260
35270
  const delegate = await optionalSource(delegatePath);
34261
35271
  if (delegate === null && !enabled)
34262
35272
  return false;
@@ -34305,8 +35315,8 @@ var replacePushRegion = (source, region, insertion) => {
34305
35315
  return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
34306
35316
  };
34307
35317
  var configureAndroid2 = async (config, plan) => {
34308
- const path = join26(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
34309
- const source = await readFile22(path, "utf8");
35318
+ const path = join28(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
35319
+ const source = await readFile25(path, "utf8");
34310
35320
  const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
34311
35321
  const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
34312
35322
  `);
@@ -34341,7 +35351,7 @@ ${content}
34341
35351
  throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
34342
35352
  const [manifestChanged, firebaseChanged] = await Promise.all([
34343
35353
  writeChangedFile2(path, nextManifest),
34344
- writeOptionalChangedFile(join26(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
35354
+ writeOptionalChangedFile(join28(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
34345
35355
  ]);
34346
35356
  return manifestChanged || firebaseChanged;
34347
35357
  };
@@ -34360,14 +35370,14 @@ init_nativeAuth();
34360
35370
 
34361
35371
  // src/mobile/nativeUpdates.ts
34362
35372
  import {
34363
- mkdir as mkdir17,
34364
- readFile as readFile23,
34365
- readdir as readdir7,
34366
- rename as rename15,
34367
- rm as rm13,
34368
- writeFile as writeFile19
35373
+ mkdir as mkdir20,
35374
+ readFile as readFile26,
35375
+ readdir as readdir8,
35376
+ rename as rename17,
35377
+ rm as rm15,
35378
+ writeFile as writeFile22
34369
35379
  } from "fs/promises";
34370
- import { dirname as dirname17, join as join27 } from "path";
35380
+ import { dirname as dirname19, join as join29 } from "path";
34371
35381
  var START = "// absolutejs:mobile-updates:start";
34372
35382
  var END = "// absolutejs:mobile-updates:end";
34373
35383
  var PLUGIN_START = "// absolutejs:mobile-update-plugin:start";
@@ -34375,32 +35385,32 @@ var PLUGIN_END = "// absolutejs:mobile-update-plugin:end";
34375
35385
  var ANDROID_PLUGIN = "AbsoluteMobileUpdateWatchdogPlugin.java";
34376
35386
  var optionalSource2 = async (path) => {
34377
35387
  try {
34378
- return await readFile23(path, "utf8");
35388
+ return await readFile26(path, "utf8");
34379
35389
  } catch (error) {
34380
35390
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
34381
35391
  return null;
34382
35392
  throw error;
34383
35393
  }
34384
35394
  };
34385
- var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35395
+ var isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34386
35396
  var writeChanged = async (path, source) => {
34387
35397
  const current = await optionalSource2(path);
34388
35398
  if (current === source)
34389
35399
  return false;
34390
- await mkdir17(dirname17(path), { recursive: true });
35400
+ await mkdir20(dirname19(path), { recursive: true });
34391
35401
  if (current === null) {
34392
- await writeFile19(path, source, { flag: "wx" });
35402
+ await writeFile22(path, source, { flag: "wx" });
34393
35403
  return true;
34394
35404
  }
34395
35405
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
34396
- await writeFile19(temporary, source, { flag: "wx" });
34397
- await rename15(temporary, path);
35406
+ await writeFile22(temporary, source, { flag: "wx" });
35407
+ await rename17(temporary, path);
34398
35408
  return true;
34399
35409
  };
34400
35410
  var removeOwnedFile = async (path) => {
34401
35411
  if (await optionalSource2(path) === null)
34402
35412
  return false;
34403
- await rm13(path);
35413
+ await rm15(path);
34404
35414
  return true;
34405
35415
  };
34406
35416
  var replaceMarkedRegion = (source, start, end, region, insertion, error) => {
@@ -34537,9 +35547,9 @@ public final class AbsoluteMobileUpdateWatchdogPlugin: CAPPlugin, CAPBridgedPlug
34537
35547
  ${PLUGIN_END}
34538
35548
  `;
34539
35549
  var configureIos3 = async (config, enabled) => {
34540
- const path = join27(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
34541
- const capacitorConfigPath = join27(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
34542
- let source = await readFile23(path, "utf8");
35550
+ const path = join29(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
35551
+ const capacitorConfigPath = join29(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
35552
+ let source = await readFile26(path, "utf8");
34543
35553
  const launch2 = source.indexOf("didFinishLaunchingWithOptions");
34544
35554
  const brace = launch2 < 0 ? -1 : source.indexOf("{", launch2);
34545
35555
  const launchRegion = enabled ? ` ${START}
@@ -34550,11 +35560,11 @@ var configureIos3 = async (config, enabled) => {
34550
35560
  `, brace) + 1, "Could not find a safe iOS location for mobile update recovery.");
34551
35561
  const plugin = enabled ? iosPluginRegion(config.updates?.bootTimeoutMs ?? 20000) : "";
34552
35562
  source = replaceMarkedRegion(source, PLUGIN_START, PLUGIN_END, plugin, source.length, "Could not find a safe iOS location for the mobile update watchdog.");
34553
- const capacitorConfigSource2 = await readFile23(capacitorConfigPath, "utf8");
35563
+ const capacitorConfigSource2 = await readFile26(capacitorConfigPath, "utf8");
34554
35564
  let capacitorConfig;
34555
35565
  try {
34556
35566
  const value = JSON.parse(capacitorConfigSource2);
34557
- if (!isRecord12(value))
35567
+ if (!isRecord15(value))
34558
35568
  throw new TypeError;
34559
35569
  capacitorConfig = value;
34560
35570
  } catch {
@@ -34723,19 +35733,19 @@ public final class AbsoluteMobileUpdateWatchdogPlugin extends Plugin {
34723
35733
  var androidActivityInRoot = async (root) => {
34724
35734
  let entries;
34725
35735
  try {
34726
- entries = await readdir7(root, { recursive: true });
35736
+ entries = await readdir8(root, { recursive: true });
34727
35737
  } catch (error) {
34728
35738
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
34729
35739
  return;
34730
35740
  throw error;
34731
35741
  }
34732
35742
  const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
34733
- return candidate ? join27(root, candidate) : undefined;
35743
+ return candidate ? join29(root, candidate) : undefined;
34734
35744
  };
34735
35745
  var androidActivity = async (config) => {
34736
35746
  const roots = [
34737
- join27(config.nativeProjectDirectory, "android/app/src/main/java"),
34738
- join27(config.nativeProjectDirectory, "android/app/src/main/kotlin")
35747
+ join29(config.nativeProjectDirectory, "android/app/src/main/java"),
35748
+ join29(config.nativeProjectDirectory, "android/app/src/main/kotlin")
34739
35749
  ];
34740
35750
  const candidates = await Promise.all(roots.map(androidActivityInRoot));
34741
35751
  const candidate = candidates.find((value) => value !== undefined);
@@ -34834,12 +35844,12 @@ var injectAndroidActivity = (source, enabled) => {
34834
35844
  };
34835
35845
  var configureAndroid3 = async (config, enabled) => {
34836
35846
  const activityPath = await androidActivity(config);
34837
- const activity = await readFile23(activityPath, "utf8");
35847
+ const activity = await readFile26(activityPath, "utf8");
34838
35848
  const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
34839
35849
  if (!packageName)
34840
35850
  throw new TypeError("Android MainActivity package declaration was not found.");
34841
35851
  const packagePath = packageName.replaceAll(".", "/");
34842
- const pluginPath = join27(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
35852
+ const pluginPath = join29(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
34843
35853
  const changed = await Promise.all([
34844
35854
  writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
34845
35855
  enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
@@ -34859,14 +35869,14 @@ var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) =>
34859
35869
  };
34860
35870
  // src/mobile/nativeObservability.ts
34861
35871
  import {
34862
- mkdir as mkdir18,
34863
- readFile as readFile24,
34864
- readdir as readdir8,
34865
- rename as rename16,
34866
- rm as rm14,
34867
- writeFile as writeFile20
35872
+ mkdir as mkdir21,
35873
+ readFile as readFile27,
35874
+ readdir as readdir9,
35875
+ rename as rename18,
35876
+ rm as rm16,
35877
+ writeFile as writeFile23
34868
35878
  } from "fs/promises";
34869
- import { dirname as dirname18, join as join28 } from "path";
35879
+ import { dirname as dirname20, join as join30 } from "path";
34870
35880
  var START2 = "// absolutejs:native-observability:start";
34871
35881
  var END2 = "// absolutejs:native-observability:end";
34872
35882
  var PLUGIN_START2 = "// absolutejs:native-observability-plugin:start";
@@ -34874,7 +35884,7 @@ var PLUGIN_END2 = "// absolutejs:native-observability-plugin:end";
34874
35884
  var ANDROID_PLUGIN2 = "AbsoluteMobileObservabilityPlugin.java";
34875
35885
  var optionalSource3 = async (path) => {
34876
35886
  try {
34877
- return await readFile24(path, "utf8");
35887
+ return await readFile27(path, "utf8");
34878
35888
  } catch (error) {
34879
35889
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
34880
35890
  return null;
@@ -34885,23 +35895,23 @@ var writeChanged2 = async (path, source) => {
34885
35895
  const current = await optionalSource3(path);
34886
35896
  if (current === source)
34887
35897
  return false;
34888
- await mkdir18(dirname18(path), { recursive: true });
35898
+ await mkdir21(dirname20(path), { recursive: true });
34889
35899
  if (current === null) {
34890
- await writeFile20(path, source, { flag: "wx" });
35900
+ await writeFile23(path, source, { flag: "wx" });
34891
35901
  return true;
34892
35902
  }
34893
35903
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
34894
- await writeFile20(temporary, source, { flag: "wx" });
34895
- await rename16(temporary, path);
35904
+ await writeFile23(temporary, source, { flag: "wx" });
35905
+ await rename18(temporary, path);
34896
35906
  return true;
34897
35907
  };
34898
35908
  var removeOwnedFile2 = async (path) => {
34899
35909
  if (await optionalSource3(path) === null)
34900
35910
  return false;
34901
- await rm14(path);
35911
+ await rm16(path);
34902
35912
  return true;
34903
35913
  };
34904
- var isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35914
+ var isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34905
35915
  var replaceMarkedRegion2 = (source, start, end, region, insertion, error) => {
34906
35916
  const existingStart = source.indexOf(start);
34907
35917
  const existingEnd = source.indexOf(end);
@@ -35034,9 +36044,9 @@ public final class AbsoluteMobileObservabilityPlugin: CAPPlugin, CAPBridgedPlugi
35034
36044
  ${PLUGIN_END2}
35035
36045
  `;
35036
36046
  var configureIos4 = async (config, enabled) => {
35037
- const path = join28(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
35038
- const capacitorConfigPath = join28(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
35039
- let source = await readFile24(path, "utf8");
36047
+ const path = join30(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
36048
+ const capacitorConfigPath = join30(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
36049
+ let source = await readFile27(path, "utf8");
35040
36050
  if (enabled && !source.includes("import MetricKit")) {
35041
36051
  const capacitorImport = source.lastIndexOf("import Capacitor");
35042
36052
  if (capacitorImport < 0)
@@ -35050,11 +36060,11 @@ ${source.slice(newline + 1)}`;
35050
36060
  source = source.replace(`import MetricKit
35051
36061
  `, "");
35052
36062
  source = replaceMarkedRegion2(source, PLUGIN_START2, PLUGIN_END2, enabled ? iosPluginRegion2 : "", source.length, "Could not find a safe iOS location for native observability.");
35053
- const capacitorConfigSource2 = await readFile24(capacitorConfigPath, "utf8");
36063
+ const capacitorConfigSource2 = await readFile27(capacitorConfigPath, "utf8");
35054
36064
  let capacitorConfig;
35055
36065
  try {
35056
36066
  const value = JSON.parse(capacitorConfigSource2);
35057
- if (!isRecord13(value))
36067
+ if (!isRecord16(value))
35058
36068
  throw new TypeError;
35059
36069
  capacitorConfig = value;
35060
36070
  } catch {
@@ -35288,19 +36298,19 @@ public final class AbsoluteMobileObservabilityPlugin extends Plugin {
35288
36298
  var androidActivityInRoot2 = async (root) => {
35289
36299
  let entries;
35290
36300
  try {
35291
- entries = await readdir8(root, { recursive: true });
36301
+ entries = await readdir9(root, { recursive: true });
35292
36302
  } catch (error) {
35293
36303
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
35294
36304
  return;
35295
36305
  throw error;
35296
36306
  }
35297
36307
  const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
35298
- return candidate ? join28(root, candidate) : undefined;
36308
+ return candidate ? join30(root, candidate) : undefined;
35299
36309
  };
35300
36310
  var androidActivity2 = async (config) => {
35301
36311
  const roots = [
35302
- join28(config.nativeProjectDirectory, "android/app/src/main/java"),
35303
- join28(config.nativeProjectDirectory, "android/app/src/main/kotlin")
36312
+ join30(config.nativeProjectDirectory, "android/app/src/main/java"),
36313
+ join30(config.nativeProjectDirectory, "android/app/src/main/kotlin")
35304
36314
  ];
35305
36315
  const candidates = await Promise.all(roots.map(androidActivityInRoot2));
35306
36316
  const candidate = candidates.find((value) => value !== undefined);
@@ -35349,148 +36359,14 @@ var injectAndroidActivity2 = (source, enabled) => {
35349
36359
  return `${source.slice(0, from)}${source.slice(through)}`;
35350
36360
  return `${source.slice(0, from)}${androidRegion2(kotlin, generatedMethod)}${source.slice(through)}`;
35351
36361
  }
35352
- if (!enabled)
35353
- return source;
35354
- const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
35355
- if (onCreate >= 0) {
35356
- const brace = source.indexOf("{", onCreate);
35357
- const insert = source.indexOf(`
35358
- `, brace) + 1;
35359
- return `${source.slice(0, insert)}${androidRegion2(kotlin, false)}${source.slice(insert)}`;
35360
- }
35361
- const close = source.lastIndexOf("}");
35362
- if (close < 0)
35363
- throw new TypeError("Could not find the Android MainActivity class body.");
35364
- const separator = source[close - 1] === `
35365
- ` ? "" : `
35366
- `;
35367
- return `${source.slice(0, close)}${separator}${androidRegion2(kotlin, true)}${source.slice(close)}`;
35368
- };
35369
- var configureAndroid4 = async (config, enabled) => {
35370
- const activityPath = await androidActivity2(config);
35371
- const activity = await readFile24(activityPath, "utf8");
35372
- const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
35373
- if (!packageName)
35374
- throw new TypeError("Android MainActivity package declaration was not found.");
35375
- const pluginPath = join28(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN2);
35376
- const changed = await Promise.all([
35377
- writeChanged2(activityPath, injectAndroidActivity2(activity, enabled)),
35378
- enabled ? writeChanged2(pluginPath, androidPluginSource2(packageName)) : removeOwnedFile2(pluginPath)
35379
- ]);
35380
- return changed.some(Boolean);
35381
- };
35382
- var applyAbsoluteNativeObservability = async (config, platforms = config.platforms) => {
35383
- if (config.engine !== "capacitor")
35384
- return { changed: false };
35385
- const enabled = config.observability !== undefined;
35386
- const changed = [];
35387
- if (platforms.includes("ios"))
35388
- changed.push(await configureIos4(config, enabled));
35389
- if (platforms.includes("android"))
35390
- changed.push(await configureAndroid4(config, enabled));
35391
- return { changed: changed.some(Boolean) };
35392
- };
35393
- // src/mobile/nativeReleaseReadiness.ts
35394
- import { mkdir as mkdir19, readFile as readFile25, readdir as readdir9, rename as rename17, writeFile as writeFile21 } from "fs/promises";
35395
- import { dirname as dirname19, join as join29 } from "path";
35396
- var START3 = "// absolutejs:release-readiness:start";
35397
- var END3 = "// absolutejs:release-readiness:end";
35398
- var IOS_PLUGIN_START = "// absolutejs:release-readiness-plugin:start";
35399
- var IOS_PLUGIN_END = "// absolutejs:release-readiness-plugin:end";
35400
- var ANDROID_PLUGIN3 = "AbsoluteReleaseReadinessPlugin.java";
35401
- var optionalSource4 = async (path) => {
35402
- try {
35403
- return await readFile25(path, "utf8");
35404
- } catch (error) {
35405
- if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
35406
- return null;
35407
- throw error;
35408
- }
35409
- };
35410
- var writeChanged3 = async (path, source) => {
35411
- const current = await optionalSource4(path);
35412
- if (current === source)
35413
- return false;
35414
- await mkdir19(dirname19(path), { recursive: true });
35415
- if (current === null) {
35416
- await writeFile21(path, source, { flag: "wx" });
35417
- return true;
35418
- }
35419
- const temporary = `${path}.${crypto.randomUUID()}.tmp`;
35420
- await writeFile21(temporary, source, { flag: "wx" });
35421
- await rename17(temporary, path);
35422
- return true;
35423
- };
35424
- var activityInRoot = async (root) => {
35425
- let entries;
35426
- try {
35427
- entries = await readdir9(root, { recursive: true });
35428
- } catch (error) {
35429
- if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
35430
- return;
35431
- throw error;
35432
- }
35433
- const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
35434
- return candidate ? join29(root, candidate) : undefined;
35435
- };
35436
- var activityPath = async (config) => {
35437
- const roots = [
35438
- join29(config.nativeProjectDirectory, "android/app/src/main/java"),
35439
- join29(config.nativeProjectDirectory, "android/app/src/main/kotlin")
35440
- ];
35441
- const candidates = await Promise.all(roots.map(activityInRoot));
35442
- const candidate = candidates.find((value) => value !== undefined);
35443
- if (candidate)
35444
- return candidate;
35445
- throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
35446
- };
35447
- var registrationRegion = (kotlin, generatedMethod) => {
35448
- if (generatedMethod)
35449
- return kotlin ? ` ${START3}
35450
- override fun onCreate(savedInstanceState: android.os.Bundle?) {
35451
- registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
35452
- super.onCreate(savedInstanceState)
35453
- }
35454
- ${END3}
35455
- ` : ` ${START3}
35456
- @Override
35457
- public void onCreate(android.os.Bundle savedInstanceState) {
35458
- registerPlugin(AbsoluteReleaseReadinessPlugin.class);
35459
- super.onCreate(savedInstanceState);
35460
- }
35461
- ${END3}
35462
- `;
35463
- return kotlin ? ` ${START3}
35464
- registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
35465
- ${END3}
35466
- ` : ` ${START3}
35467
- registerPlugin(AbsoluteReleaseReadinessPlugin.class);
35468
- ${END3}
35469
- `;
35470
- };
35471
- var injectRegistration = (source) => {
35472
- const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
35473
- const existingStart = source.indexOf(START3);
35474
- const existingEnd = source.indexOf(END3);
35475
- if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
35476
- throw new TypeError("AbsoluteJS release-readiness markers are malformed.");
35477
- if (existingStart >= 0) {
35478
- const from = source.lastIndexOf(`
35479
- `, existingStart) + 1;
35480
- const newline = source.indexOf(`
35481
- `, existingEnd + END3.length);
35482
- const through = newline < 0 ? source.length : newline + 1;
35483
- const generatedMethod = source.slice(from, through).includes("onCreate(");
35484
- return `${source.slice(0, from)}${registrationRegion(kotlin, generatedMethod)}${source.slice(through)}`;
35485
- }
36362
+ if (!enabled)
36363
+ return source;
35486
36364
  const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
35487
36365
  if (onCreate >= 0) {
35488
36366
  const brace = source.indexOf("{", onCreate);
35489
36367
  const insert = source.indexOf(`
35490
36368
  `, brace) + 1;
35491
- if (insert <= 0)
35492
- throw new TypeError("Could not inject Android release readiness into MainActivity.");
35493
- return `${source.slice(0, insert)}${registrationRegion(kotlin, false)}${source.slice(insert)}`;
36369
+ return `${source.slice(0, insert)}${androidRegion2(kotlin, false)}${source.slice(insert)}`;
35494
36370
  }
35495
36371
  const close = source.lastIndexOf("}");
35496
36372
  if (close < 0)
@@ -35498,633 +36374,254 @@ var injectRegistration = (source) => {
35498
36374
  const separator = source[close - 1] === `
35499
36375
  ` ? "" : `
35500
36376
  `;
35501
- return `${source.slice(0, close)}${separator}${registrationRegion(kotlin, true)}${source.slice(close)}`;
35502
- };
35503
- var pluginSource = (packageName) => `package ${packageName};
35504
-
35505
- import android.util.Log;
35506
- import com.getcapacitor.JSObject;
35507
- import com.getcapacitor.Plugin;
35508
- import com.getcapacitor.PluginCall;
35509
- import com.getcapacitor.PluginMethod;
35510
- import com.getcapacitor.annotation.CapacitorPlugin;
35511
-
35512
- @CapacitorPlugin(name = "AbsoluteReleaseReadiness")
35513
- public final class AbsoluteReleaseReadinessPlugin extends Plugin {
35514
- @PluginMethod
35515
- public void ready(PluginCall call) {
35516
- Log.i("AbsoluteJS", "Capacitor embedded web content ready");
35517
- call.resolve(new JSObject().put("ready", true));
35518
- }
35519
- }
35520
- `;
35521
- var iosPluginRegion3 = `${IOS_PLUGIN_START}
35522
- @objc(AbsoluteReleaseReadinessPlugin)
35523
- public final class AbsoluteReleaseReadinessPlugin: CAPPlugin, CAPBridgedPlugin {
35524
- public let identifier = "AbsoluteReleaseReadinessPlugin"
35525
- public let jsName = "AbsoluteReleaseReadiness"
35526
- public let pluginMethods: [CAPPluginMethod] = [
35527
- CAPPluginMethod(name: "ready", returnType: CAPPluginReturnPromise)
35528
- ]
35529
-
35530
- @objc public func ready(_ call: CAPPluginCall) {
35531
- let info = Bundle.main.infoDictionary ?? [:]
35532
- let version = info["CFBundleShortVersionString"] as? String ?? "unknown"
35533
- let build = info["CFBundleVersion"] as? String ?? "unknown"
35534
- NSLog("AbsoluteJS: Capacitor embedded web content ready; version=%@; build=%@", version, build)
35535
- call.resolve(["ready": true])
35536
- }
35537
- }
35538
- ${IOS_PLUGIN_END}
35539
- `;
35540
- var replaceIosPluginRegion = (appDelegate, existingStart, existingEnd) => {
35541
- const from = appDelegate.lastIndexOf(`
35542
- `, existingStart) + 1;
35543
- const newline = appDelegate.indexOf(`
35544
- `, existingEnd + IOS_PLUGIN_END.length);
35545
- const through = newline < 0 ? appDelegate.length : newline + 1;
35546
- return `${appDelegate.slice(0, from)}${iosPluginRegion3}${appDelegate.slice(through)}`;
35547
- };
35548
- var configureIos5 = async (config) => {
35549
- const appDelegatePath = join29(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
35550
- const capacitorConfigPath = join29(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
35551
- const appDelegate = await readFile25(appDelegatePath, "utf8");
35552
- const existingStart = appDelegate.indexOf(IOS_PLUGIN_START);
35553
- const existingEnd = appDelegate.indexOf(IOS_PLUGIN_END);
35554
- if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
35555
- throw new TypeError("AbsoluteJS iOS release-readiness markers are malformed.");
35556
- const source = existingStart < 0 ? `${appDelegate.trimEnd()}
35557
-
35558
- ${iosPluginRegion3}` : replaceIosPluginRegion(appDelegate, existingStart, existingEnd);
35559
- let capacitorConfig;
35560
- try {
35561
- capacitorConfig = JSON.parse(await readFile25(capacitorConfigPath, "utf8"));
35562
- } catch {
35563
- throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting release readiness.");
35564
- }
35565
- if (typeof capacitorConfig !== "object" || capacitorConfig === null)
35566
- throw new TypeError("iOS capacitor.config.json must contain an object.");
35567
- const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
35568
- if (!Array.isArray(packageClassList))
35569
- throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting release readiness.");
35570
- const classes = packageClassList.filter((value) => typeof value === "string");
35571
- const nextConfig = `${JSON.stringify({
35572
- ...capacitorConfig,
35573
- packageClassList: [
35574
- ...new Set([...classes, "AbsoluteReleaseReadinessPlugin"])
35575
- ]
35576
- }, null, "\t")}
35577
- `;
35578
- const changed = await Promise.all([
35579
- writeChanged3(appDelegatePath, source),
35580
- writeChanged3(capacitorConfigPath, nextConfig)
35581
- ]);
35582
- return changed.some(Boolean);
36377
+ return `${source.slice(0, close)}${separator}${androidRegion2(kotlin, true)}${source.slice(close)}`;
35583
36378
  };
35584
- var configureAndroid5 = async (config) => {
35585
- const mainActivityPath = await activityPath(config);
35586
- const activity = await readFile25(mainActivityPath, "utf8");
36379
+ var configureAndroid4 = async (config, enabled) => {
36380
+ const activityPath = await androidActivity2(config);
36381
+ const activity = await readFile27(activityPath, "utf8");
35587
36382
  const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
35588
- if (!packageName)
35589
- throw new TypeError("Android MainActivity package declaration was not found.");
35590
- const pluginPath = join29(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN3);
35591
- const changed = await Promise.all([
35592
- writeChanged3(mainActivityPath, injectRegistration(activity)),
35593
- writeChanged3(pluginPath, pluginSource(packageName))
35594
- ]);
35595
- return changed.some(Boolean);
35596
- };
35597
- var applyAbsoluteNativeReleaseReadiness = async (config, platforms = config.platforms) => {
35598
- if (config.engine !== "capacitor")
35599
- return { changed: false };
35600
- const changed = await Promise.all([
35601
- platforms.includes("android") ? configureAndroid5(config) : false,
35602
- platforms.includes("ios") ? configureIos5(config) : false
35603
- ]);
35604
- return { changed: changed.some(Boolean) };
35605
- };
35606
-
35607
- // src/mobile/index.ts
35608
- init_pageProtocol();
35609
- init_producerContext();
35610
- init_releaseArtifact();
35611
-
35612
- // src/mobile/releaseCertification.ts
35613
- import { createHash as createHash16 } from "crypto";
35614
- import {
35615
- access as access17,
35616
- mkdir as mkdir20,
35617
- mkdtemp as mkdtemp10,
35618
- readFile as readFile26,
35619
- rename as rename18,
35620
- rm as rm15,
35621
- writeFile as writeFile22
35622
- } from "fs/promises";
35623
- import { basename as basename8, isAbsolute as isAbsolute9, join as join30, relative as relative16, resolve as resolve18, sep as sep11 } from "path";
35624
- var ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT = 1;
35625
- var CERTIFICATION_PREFIX = "amobile_cert_";
35626
- var SHA256 = /^[a-f0-9]{64}$/u;
35627
- var STRENGTH_RANK = {
35628
- device: 1,
35629
- installed: 0,
35630
- simulator: 0,
35631
- store: 2
35632
- };
35633
- var isRequirement = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
35634
- var isStrength = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
35635
- var isArtifactExactness = (value) => value === "archive-equivalent" || value === "source-equivalent" || value === "store-delivered";
35636
- var isDistribution = (value) => value === "apple-processed" || value === "registered-device" || value === "simulator-release";
35637
- var isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35638
- var inside4 = (root, candidate) => {
35639
- const path = relative16(resolve18(root), resolve18(candidate));
35640
- return path === "" || !isAbsolute9(path) && path !== ".." && !path.startsWith(`..${sep11}`);
35641
- };
35642
- var exists9 = async (path) => {
35643
- try {
35644
- await access17(path);
35645
- return true;
35646
- } catch {
35647
- return false;
35648
- }
35649
- };
35650
- var sha2564 = (bytes) => createHash16("sha256").update(bytes).digest("hex");
35651
- var requireString2 = (value, name) => {
35652
- if (typeof value !== "string" || !value)
35653
- throw new TypeError(`Mobile certification ${name} is invalid.`);
35654
- return value;
35655
- };
35656
- var requirePositiveInteger = (value, name) => {
35657
- if (!Number.isSafeInteger(value) || Number(value) < 1)
35658
- throw new TypeError(`Mobile certification ${name} is invalid.`);
35659
- return Number(value);
35660
- };
35661
- var resolveEvidencePath = async (projectRoot, requested) => {
35662
- const selected = resolve18(projectRoot, requested);
35663
- if (!inside4(projectRoot, selected))
35664
- throw new TypeError("Mobile certification evidence must remain inside the project.");
35665
- if (basename8(selected) === "report.json")
35666
- return selected;
35667
- const report = join30(selected, "report.json");
35668
- if (!await exists9(report))
35669
- throw new TypeError(`Mobile certification evidence does not contain report.json: ${requested}`);
35670
- return report;
35671
- };
35672
- var releaseIdentity = (release) => {
35673
- const { metadata } = release;
35674
- if (!metadata.signed)
35675
- throw new TypeError("Mobile release certification requires a signed native release.");
35676
- const common = {
35677
- appBuild: metadata.appBuild,
35678
- appId: metadata.appId,
35679
- artifactBytes: metadata.bytes,
35680
- artifactSha256: metadata.sha256,
35681
- engine: metadata.engine,
35682
- platform: metadata.platform,
35683
- releaseId: metadata.releaseId,
35684
- runtime: metadata.runtime,
35685
- signed: true
35686
- };
35687
- if (metadata.platform === "android")
35688
- return {
35689
- ...common,
35690
- ...metadata.versionCode === undefined ? {} : { versionCode: metadata.versionCode }
35691
- };
35692
- return {
35693
- ...common,
35694
- ...metadata.buildNumber === undefined ? {} : { buildNumber: metadata.buildNumber },
35695
- marketingVersion: metadata.marketingVersion
35696
- };
35697
- };
35698
- var requireCheck = (report, id, accepted = ["PASS"]) => {
35699
- const checks = report.automatedChecks;
35700
- if (!Array.isArray(checks))
35701
- throw new TypeError("Mobile certification evidence has invalid automated checks.");
35702
- const check = checks.find((candidate) => isRecord14(candidate) && candidate.id === id);
35703
- if (!isRecord14(check) || !accepted.includes(String(check.result)))
35704
- throw new TypeError(`Mobile certification evidence does not satisfy ${id}.`);
35705
- };
35706
- var validateCommonReport = (report, release) => {
35707
- if (report.reportVersion !== 1 || report.platform !== release.platform || !isRecord14(report.metadata) || report.metadata.provider !== release.engine || !isRecord14(report.run) || report.run.status !== "pass" || report.run.appId !== release.appId)
35708
- throw new TypeError("Mobile certification evidence does not match the release platform, provider, application, or successful-run contract.");
35709
- requireCheck(report, "AUTO-DEV-01");
35710
- return {
35711
- generatedAt: requireString2(report.generatedAt, "evidence generatedAt"),
35712
- run: report.run
35713
- };
35714
- };
35715
- var validateReleaseFields = (value, release) => {
35716
- if (value.releaseId !== release.releaseId || value.artifactSha256 !== release.artifactSha256 || value.artifactBytes !== release.artifactBytes || value.engine !== release.engine || value.signed !== true)
35717
- throw new TypeError("Mobile certification evidence does not match the immutable release identity.");
35718
- };
35719
- var validateAndroidEvidence = (report, run, release, reportSha256, generatedAt) => {
35720
- if (!isRecord14(run.release))
35721
- throw new TypeError("Android certification evidence has no installed release result.");
35722
- validateReleaseFields(run.release, release);
35723
- if (run.release.embeddedOffline !== true)
35724
- throw new TypeError("Android certification evidence did not prove embedded offline relaunch.");
35725
- requireCheck(report, "AUTO-RELEASE-01");
35726
- requireCheck(report, "AUTO-RELEASE-OFFLINE-01");
35727
- return {
35728
- generatedAt,
35729
- networkUnavailable: "proven",
35730
- remote: false,
35731
- reportSha256,
35732
- strength: "installed"
35733
- };
35734
- };
35735
- var validateIosEvidence = (report, run, release, reportSha256, generatedAt) => {
35736
- if (!isRecord14(run.iosRelease))
35737
- throw new TypeError("iOS certification evidence has no installed release result.");
35738
- const ios = run.iosRelease;
35739
- validateReleaseFields(ios, release);
35740
- if (ios.embeddedLocal !== true)
35741
- throw new TypeError("iOS certification evidence did not prove embedded local rendering.");
35742
- requireCheck(report, "AUTO-IOS-RELEASE-01");
35743
- let strength;
35744
- if (ios.distribution === "simulator-release" && ios.artifactExactness === "source-equivalent" && run.targetKind === "simulator" && ios.networkUnavailable === "not-proven")
35745
- strength = "simulator";
35746
- else if (ios.distribution === "registered-device" && ios.artifactExactness === "archive-equivalent" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
35747
- strength = "device";
35748
- else if (ios.distribution === "apple-processed" && ios.artifactExactness === "store-delivered" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
35749
- strength = "store";
35750
- else
35751
- throw new TypeError("iOS certification evidence contains an invalid target, distribution, exactness, or offline-strength combination.");
35752
- if (strength === "simulator")
35753
- requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01", ["SKIPPED"]);
35754
- else
35755
- requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01");
35756
- return {
35757
- artifactExactness: ios.artifactExactness,
35758
- distribution: ios.distribution,
35759
- generatedAt,
35760
- networkUnavailable: ios.networkUnavailable === "user-confirmed" ? "proven" : "not-proven",
35761
- remote: ios.remote === true,
35762
- reportSha256,
35763
- strength
35764
- };
36383
+ if (!packageName)
36384
+ throw new TypeError("Android MainActivity package declaration was not found.");
36385
+ const pluginPath = join30(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN2);
36386
+ const changed = await Promise.all([
36387
+ writeChanged2(activityPath, injectAndroidActivity2(activity, enabled)),
36388
+ enabled ? writeChanged2(pluginPath, androidPluginSource2(packageName)) : removeOwnedFile2(pluginPath)
36389
+ ]);
36390
+ return changed.some(Boolean);
35765
36391
  };
35766
- var validateEvidence = async (projectRoot, requested, release) => {
35767
- const path = await resolveEvidencePath(projectRoot, requested);
35768
- const bytes = await readFile26(path);
35769
- let value;
36392
+ var applyAbsoluteNativeObservability = async (config, platforms = config.platforms) => {
36393
+ if (config.engine !== "capacitor")
36394
+ return { changed: false };
36395
+ const enabled = config.observability !== undefined;
36396
+ const changed = [];
36397
+ if (platforms.includes("ios"))
36398
+ changed.push(await configureIos4(config, enabled));
36399
+ if (platforms.includes("android"))
36400
+ changed.push(await configureAndroid4(config, enabled));
36401
+ return { changed: changed.some(Boolean) };
36402
+ };
36403
+ // src/mobile/nativeReleaseReadiness.ts
36404
+ import { mkdir as mkdir22, readFile as readFile28, readdir as readdir10, rename as rename19, writeFile as writeFile24 } from "fs/promises";
36405
+ import { dirname as dirname21, join as join31 } from "path";
36406
+ var START3 = "// absolutejs:release-readiness:start";
36407
+ var END3 = "// absolutejs:release-readiness:end";
36408
+ var IOS_PLUGIN_START = "// absolutejs:release-readiness-plugin:start";
36409
+ var IOS_PLUGIN_END = "// absolutejs:release-readiness-plugin:end";
36410
+ var ANDROID_PLUGIN3 = "AbsoluteReleaseReadinessPlugin.java";
36411
+ var optionalSource4 = async (path) => {
35770
36412
  try {
35771
- value = JSON.parse(bytes.toString("utf8"));
35772
- } catch {
35773
- throw new TypeError("Mobile certification evidence report is not valid JSON.");
36413
+ return await readFile28(path, "utf8");
36414
+ } catch (error) {
36415
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
36416
+ return null;
36417
+ throw error;
35774
36418
  }
35775
- if (!isRecord14(value))
35776
- throw new TypeError("Mobile certification evidence report must contain an object.");
35777
- const { generatedAt, run } = validateCommonReport(value, release);
35778
- const digest = sha2564(bytes);
35779
- return release.platform === "android" ? validateAndroidEvidence(value, run, release, digest, generatedAt) : validateIosEvidence(value, run, release, digest, generatedAt);
35780
- };
35781
- var defaultRequirement = (platform4) => platform4 === "android" ? "installed" : "simulator";
35782
- var validateRequirement = (platform4, requirement) => {
35783
- if (platform4 === "android" && requirement !== "installed")
35784
- throw new TypeError("Android certification currently uses its fixed installed/offline requirement; --require simulator, device, and store are iOS policies.");
35785
- if (platform4 === "ios" && requirement === "installed")
35786
- throw new TypeError("iOS certification requires simulator, device, or store evidence.");
35787
36419
  };
35788
- var satisfiesRequirement = (strength, requirement) => {
35789
- if (requirement === "installed")
35790
- return strength === "installed";
35791
- if (strength === "installed")
36420
+ var writeChanged3 = async (path, source) => {
36421
+ const current = await optionalSource4(path);
36422
+ if (current === source)
35792
36423
  return false;
35793
- return STRENGTH_RANK[strength] >= STRENGTH_RANK[requirement];
35794
- };
35795
- var resolveAbsoluteMobileCertificationRequirement = (config, target) => {
35796
- const channelRequirement = target.channel ? config.releaseCertification.channels[target.channel]?.[target.platform] : undefined;
35797
- const playRequirement = target.googlePlayTrack ? config.releaseCertification.googlePlayTracks[target.googlePlayTrack] : undefined;
35798
- const requirements = [channelRequirement, playRequirement].filter((value) => value !== undefined);
35799
- if (requirements.length === 0)
35800
- return;
35801
- return requirements.reduce((strongest, candidate) => STRENGTH_RANK[candidate] > STRENGTH_RANK[strongest] ? candidate : strongest);
35802
- };
35803
- var validateCertifiedEvidence = (platform4, evidence) => {
35804
- if (platform4 === "android") {
35805
- if (evidence.strength !== "installed" || evidence.networkUnavailable !== "proven" || evidence.artifactExactness !== undefined || evidence.distribution !== undefined || evidence.remote)
35806
- throw new TypeError("Android release certification contains invalid evidence strength.");
35807
- return;
36424
+ await mkdir22(dirname21(path), { recursive: true });
36425
+ if (current === null) {
36426
+ await writeFile24(path, source, { flag: "wx" });
36427
+ return true;
35808
36428
  }
35809
- const simulator = evidence.strength === "simulator" && evidence.artifactExactness === "source-equivalent" && evidence.distribution === "simulator-release" && evidence.networkUnavailable === "not-proven";
35810
- const device = evidence.strength === "device" && evidence.artifactExactness === "archive-equivalent" && evidence.distribution === "registered-device" && evidence.networkUnavailable === "proven";
35811
- const store = evidence.strength === "store" && evidence.artifactExactness === "store-delivered" && evidence.distribution === "apple-processed" && evidence.networkUnavailable === "proven";
35812
- if (!simulator && !device && !store)
35813
- throw new TypeError("iOS release certification contains invalid evidence strength.");
36429
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
36430
+ await writeFile24(temporary, source, { flag: "wx" });
36431
+ await rename19(temporary, path);
36432
+ return true;
35814
36433
  };
35815
- var canonicalJsonValue = (value) => {
35816
- if (Array.isArray(value))
35817
- return value.map(canonicalJsonValue);
35818
- if (!isRecord14(value))
35819
- return value;
35820
- const sorted = {};
35821
- for (const key of Object.keys(value).sort())
35822
- sorted[key] = canonicalJsonValue(value[key]);
35823
- return sorted;
36434
+ var activityInRoot = async (root) => {
36435
+ let entries;
36436
+ try {
36437
+ entries = await readdir10(root, { recursive: true });
36438
+ } catch (error) {
36439
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
36440
+ return;
36441
+ throw error;
36442
+ }
36443
+ const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
36444
+ return candidate ? join31(root, candidate) : undefined;
35824
36445
  };
35825
- var certificationBody = (certification) => JSON.stringify(canonicalJsonValue(certification));
35826
- var createAbsoluteMobileReleaseCertification = async (options) => {
35827
- if (options.evidencePaths.length === 0)
35828
- throw new TypeError("Mobile release certification requires at least one --evidence report.");
35829
- const release = releaseIdentity(options.release);
35830
- const requirement = options.requirement ?? defaultRequirement(release.platform);
35831
- validateRequirement(release.platform, requirement);
35832
- const evidence = await Promise.all(options.evidencePaths.map((path) => validateEvidence(options.projectRoot, path, release)));
35833
- evidence.sort((left, right) => left.reportSha256.localeCompare(right.reportSha256));
35834
- const [firstEvidence] = evidence;
35835
- if (!firstEvidence)
35836
- throw new TypeError("Mobile release certification requires valid evidence.");
35837
- const strength = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, firstEvidence.strength);
35838
- if (!satisfiesRequirement(strength, requirement))
35839
- throw new TypeError(`Mobile release evidence strength ${strength} does not satisfy required ${requirement} certification.`);
35840
- const body = {
35841
- evidence,
35842
- format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
35843
- generatedAt: options.generatedAt ?? evidence.reduce((latest, candidate) => candidate.generatedAt > latest ? candidate.generatedAt : latest, firstEvidence.generatedAt),
35844
- release,
35845
- requirement,
35846
- status: "certified",
35847
- strength
35848
- };
35849
- return {
35850
- certificationId: `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`,
35851
- ...body
35852
- };
36446
+ var activityPath = async (config) => {
36447
+ const roots = [
36448
+ join31(config.nativeProjectDirectory, "android/app/src/main/java"),
36449
+ join31(config.nativeProjectDirectory, "android/app/src/main/kotlin")
36450
+ ];
36451
+ const candidates = await Promise.all(roots.map(activityInRoot));
36452
+ const candidate = candidates.find((value) => value !== undefined);
36453
+ if (candidate)
36454
+ return candidate;
36455
+ throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
35853
36456
  };
35854
- var parseCertification = (value) => {
35855
- if (!isRecord14(value) || value.format !== ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT || value.status !== "certified" || !isRecord14(value.release) || !Array.isArray(value.evidence))
35856
- throw new TypeError("Mobile release certification contract is invalid.");
35857
- const { release } = value;
35858
- const { platform: platform4 } = release;
35859
- const { engine } = release;
35860
- if (platform4 !== "android" && platform4 !== "ios" || engine !== "capacitor" && engine !== "expo" || release.signed !== true)
35861
- throw new TypeError("Mobile release certification identity is invalid.");
35862
- const { requirement } = value;
35863
- const { strength } = value;
35864
- if (!isRequirement(requirement) || !isStrength(strength))
35865
- throw new TypeError("Mobile release certification policy is invalid.");
35866
- validateRequirement(platform4, requirement);
35867
- if (!satisfiesRequirement(strength, requirement))
35868
- throw new TypeError("Mobile release certification does not satisfy its declared policy.");
35869
- const artifactBytes = requirePositiveInteger(release.artifactBytes, "release artifactBytes");
35870
- const artifactSha256 = requireString2(release.artifactSha256, "release artifactSha256");
35871
- if (!SHA256.test(artifactSha256))
35872
- throw new TypeError("Mobile release certification artifact digest is invalid.");
35873
- const parsedRelease = {
35874
- appBuild: requireString2(release.appBuild, "release appBuild"),
35875
- appId: requireString2(release.appId, "release appId"),
35876
- artifactBytes,
35877
- artifactSha256,
35878
- engine,
35879
- platform: platform4,
35880
- releaseId: requireString2(release.releaseId, "release releaseId"),
35881
- runtime: requireString2(release.runtime, "release runtime"),
35882
- signed: true,
35883
- ...release.buildNumber === undefined ? {} : {
35884
- buildNumber: requirePositiveInteger(release.buildNumber, "release buildNumber")
35885
- },
35886
- ...release.marketingVersion === undefined ? {} : {
35887
- marketingVersion: requireString2(release.marketingVersion, "release marketingVersion")
35888
- },
35889
- ...release.versionCode === undefined ? {} : {
35890
- versionCode: requirePositiveInteger(release.versionCode, "release versionCode")
36457
+ var registrationRegion = (kotlin, generatedMethod) => {
36458
+ if (generatedMethod)
36459
+ return kotlin ? ` ${START3}
36460
+ override fun onCreate(savedInstanceState: android.os.Bundle?) {
36461
+ registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
36462
+ super.onCreate(savedInstanceState)
35891
36463
  }
35892
- };
35893
- const evidence = value.evidence.map((candidate) => {
35894
- if (!isRecord14(candidate) || !SHA256.test(String(candidate.reportSha256)) || !isStrength(candidate.strength) || candidate.networkUnavailable !== "not-proven" && candidate.networkUnavailable !== "proven" || typeof candidate.remote !== "boolean" || typeof candidate.generatedAt !== "string" || candidate.artifactExactness !== undefined && !isArtifactExactness(candidate.artifactExactness) || candidate.distribution !== undefined && !isDistribution(candidate.distribution))
35895
- throw new TypeError("Mobile release certification evidence is invalid.");
35896
- const parsedEvidence = {
35897
- generatedAt: candidate.generatedAt,
35898
- networkUnavailable: candidate.networkUnavailable,
35899
- remote: candidate.remote,
35900
- reportSha256: String(candidate.reportSha256),
35901
- strength: candidate.strength,
35902
- ...candidate.artifactExactness === undefined ? {} : {
35903
- artifactExactness: candidate.artifactExactness
35904
- },
35905
- ...candidate.distribution === undefined ? {} : { distribution: candidate.distribution }
35906
- };
35907
- validateCertifiedEvidence(platform4, parsedEvidence);
35908
- return parsedEvidence;
35909
- });
35910
- const strongestEvidence = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, evidence[0]?.strength ?? "installed");
35911
- if (evidence.length === 0 || strongestEvidence !== strength)
35912
- throw new TypeError("Mobile release certification evidence does not support its declared strength.");
35913
- const parsed = {
35914
- certificationId: requireString2(value.certificationId, "certificationId"),
35915
- evidence,
35916
- format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
35917
- generatedAt: requireString2(value.generatedAt, "generatedAt"),
35918
- release: parsedRelease,
35919
- requirement,
35920
- status: "certified",
35921
- strength
35922
- };
35923
- const { certificationId, ...body } = parsed;
35924
- if (certificationId !== `${CERTIFICATION_PREFIX}${sha2564(certificationBody(body))}`)
35925
- throw new TypeError("Mobile release certification content digest is invalid.");
35926
- return parsed;
35927
- };
35928
- var readAbsoluteMobileReleaseCertification = async (projectRoot, requested) => {
35929
- const selected = resolve18(projectRoot, requested);
35930
- if (!inside4(projectRoot, selected))
35931
- throw new TypeError("Mobile release certification must remain inside the project.");
35932
- const path = basename8(selected) === "certification.json" ? selected : join30(selected, "certification.json");
35933
- const value = JSON.parse(await readFile26(path, "utf8"));
35934
- return { certification: parseCertification(value), path };
36464
+ ${END3}
36465
+ ` : ` ${START3}
36466
+ @Override
36467
+ public void onCreate(android.os.Bundle savedInstanceState) {
36468
+ registerPlugin(AbsoluteReleaseReadinessPlugin.class);
36469
+ super.onCreate(savedInstanceState);
36470
+ }
36471
+ ${END3}
36472
+ `;
36473
+ return kotlin ? ` ${START3}
36474
+ registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
36475
+ ${END3}
36476
+ ` : ` ${START3}
36477
+ registerPlugin(AbsoluteReleaseReadinessPlugin.class);
36478
+ ${END3}
36479
+ `;
35935
36480
  };
35936
- var renderAbsoluteMobileReleaseCertification = (certification) => {
35937
- const { release } = certification;
35938
- const version = release.platform === "ios" ? `${release.marketingVersion ?? "unknown"} (${release.buildNumber ?? "unallocated"})` : String(release.versionCode ?? "unallocated");
35939
- const evidence = certification.evidence.map((item) => `| ${item.strength} | ${item.networkUnavailable} | ${item.remote ? "paired Mac" : "local host"} | ${item.reportSha256} |`).join(`
35940
- `);
35941
- return `# AbsoluteJS mobile release certification
35942
-
35943
- - Status: CERTIFIED
35944
- - Certification: ${certification.certificationId}
35945
- - Generated: ${certification.generatedAt}
35946
- - Platform: ${release.platform}
35947
- - Engine: ${release.engine}
35948
- - Requirement: ${certification.requirement}
35949
- - Evidence strength: ${certification.strength}
35950
- - Release: ${release.releaseId}
35951
- - Artifact SHA-256: ${release.artifactSha256}
35952
- - Runtime fingerprint: ${release.runtime}
35953
- - Embedded app build: ${release.appBuild}
35954
- - Version/build: ${version}
36481
+ var injectRegistration = (source) => {
36482
+ const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
36483
+ const existingStart = source.indexOf(START3);
36484
+ const existingEnd = source.indexOf(END3);
36485
+ if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
36486
+ throw new TypeError("AbsoluteJS release-readiness markers are malformed.");
36487
+ if (existingStart >= 0) {
36488
+ const from = source.lastIndexOf(`
36489
+ `, existingStart) + 1;
36490
+ const newline = source.indexOf(`
36491
+ `, existingEnd + END3.length);
36492
+ const through = newline < 0 ? source.length : newline + 1;
36493
+ const generatedMethod = source.slice(from, through).includes("onCreate(");
36494
+ return `${source.slice(0, from)}${registrationRegion(kotlin, generatedMethod)}${source.slice(through)}`;
36495
+ }
36496
+ const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
36497
+ if (onCreate >= 0) {
36498
+ const brace = source.indexOf("{", onCreate);
36499
+ const insert = source.indexOf(`
36500
+ `, brace) + 1;
36501
+ if (insert <= 0)
36502
+ throw new TypeError("Could not inject Android release readiness into MainActivity.");
36503
+ return `${source.slice(0, insert)}${registrationRegion(kotlin, false)}${source.slice(insert)}`;
36504
+ }
36505
+ const close = source.lastIndexOf("}");
36506
+ if (close < 0)
36507
+ throw new TypeError("Could not find the Android MainActivity class body.");
36508
+ const separator = source[close - 1] === `
36509
+ ` ? "" : `
36510
+ `;
36511
+ return `${source.slice(0, close)}${separator}${registrationRegion(kotlin, true)}${source.slice(close)}`;
36512
+ };
36513
+ var pluginSource = (packageName) => `package ${packageName};
35955
36514
 
35956
- This certification is local and contains no signing credentials, device identifiers, SSH destinations, report paths, application data, or page contents. Re-run verification against the immutable release before using it as a CI or promotion gate.
36515
+ import android.util.Log;
36516
+ import com.getcapacitor.JSObject;
36517
+ import com.getcapacitor.Plugin;
36518
+ import com.getcapacitor.PluginCall;
36519
+ import com.getcapacitor.PluginMethod;
36520
+ import com.getcapacitor.annotation.CapacitorPlugin;
35957
36521
 
35958
- | Strength | Network unavailable | Execution | Report SHA-256 |
35959
- | --- | --- | --- | --- |
35960
- ${evidence}
36522
+ @CapacitorPlugin(name = "AbsoluteReleaseReadiness")
36523
+ public final class AbsoluteReleaseReadinessPlugin extends Plugin {
36524
+ @PluginMethod
36525
+ public void ready(PluginCall call) {
36526
+ Log.i("AbsoluteJS", "Capacitor embedded web content ready");
36527
+ call.resolve(new JSObject().put("ready", true));
36528
+ }
36529
+ }
35961
36530
  `;
35962
- };
35963
- var serializeAbsoluteMobileReleaseCertification = (certification) => `${JSON.stringify(certification, null, 2)}
36531
+ var iosPluginRegion3 = `${IOS_PLUGIN_START}
36532
+ @objc(AbsoluteReleaseReadinessPlugin)
36533
+ public final class AbsoluteReleaseReadinessPlugin: CAPPlugin, CAPBridgedPlugin {
36534
+ public let identifier = "AbsoluteReleaseReadinessPlugin"
36535
+ public let jsName = "AbsoluteReleaseReadiness"
36536
+ public let pluginMethods: [CAPPluginMethod] = [
36537
+ CAPPluginMethod(name: "ready", returnType: CAPPluginReturnPromise)
36538
+ ]
36539
+
36540
+ @objc public func ready(_ call: CAPPluginCall) {
36541
+ let info = Bundle.main.infoDictionary ?? [:]
36542
+ let version = info["CFBundleShortVersionString"] as? String ?? "unknown"
36543
+ let build = info["CFBundleVersion"] as? String ?? "unknown"
36544
+ NSLog("AbsoluteJS: Capacitor embedded web content ready; version=%@; build=%@", version, build)
36545
+ call.resolve(["ready": true])
36546
+ }
36547
+ }
36548
+ ${IOS_PLUGIN_END}
35964
36549
  `;
35965
- var verifyAbsoluteMobileReleaseCertification = (certification, release, requirement) => {
35966
- const parsed = parseCertification(certification);
35967
- const expected = releaseIdentity(release);
35968
- if (JSON.stringify(parsed.release) !== JSON.stringify(expected))
35969
- throw new TypeError("Mobile release certification was invalidated by a release identity, artifact, runtime, signing, version, build, or embedded-bundle change.");
35970
- if (requirement) {
35971
- validateRequirement(expected.platform, requirement);
35972
- if (!satisfiesRequirement(parsed.strength, requirement))
35973
- throw new TypeError(`Mobile release certification strength ${parsed.strength} does not satisfy required ${requirement} policy.`);
35974
- }
35975
- return parsed;
36550
+ var replaceIosPluginRegion = (appDelegate, existingStart, existingEnd) => {
36551
+ const from = appDelegate.lastIndexOf(`
36552
+ `, existingStart) + 1;
36553
+ const newline = appDelegate.indexOf(`
36554
+ `, existingEnd + IOS_PLUGIN_END.length);
36555
+ const through = newline < 0 ? appDelegate.length : newline + 1;
36556
+ return `${appDelegate.slice(0, from)}${iosPluginRegion3}${appDelegate.slice(through)}`;
35976
36557
  };
35977
- var writeAbsoluteMobileReleaseCertification = async (projectRoot, certification, outputDirectory) => {
35978
- const root = resolve18(projectRoot);
35979
- const parent = resolve18(root, outputDirectory ?? join30(".absolutejs", "mobile", "certifications", certification.release.platform, certification.release.releaseId));
35980
- if (!inside4(root, parent))
35981
- throw new TypeError("Mobile certification output must remain inside the project.");
35982
- await mkdir20(parent, { recursive: true });
35983
- const destination = join30(parent, certification.certificationId);
35984
- const paths = {
35985
- directory: destination,
35986
- jsonPath: join30(destination, "certification.json"),
35987
- markdownPath: join30(destination, "certification.md")
35988
- };
35989
- const json = serializeAbsoluteMobileReleaseCertification(certification);
35990
- const markdown = renderAbsoluteMobileReleaseCertification(certification);
35991
- const verifyExisting = async () => {
35992
- const [existingJson, existingMarkdown] = await Promise.all([
35993
- readFile26(paths.jsonPath, "utf8"),
35994
- readFile26(paths.markdownPath, "utf8")
35995
- ]);
35996
- if (existingJson !== json || existingMarkdown !== markdown)
35997
- throw new TypeError(`Immutable mobile certification ${certification.certificationId} already exists with different content.`);
35998
- };
35999
- if (await exists9(destination)) {
36000
- await verifyExisting();
36001
- return paths;
36002
- }
36003
- const staging = await mkdtemp10(join30(parent, ".certification-"));
36558
+ var configureIos5 = async (config) => {
36559
+ const appDelegatePath = join31(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
36560
+ const capacitorConfigPath = join31(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
36561
+ const appDelegate = await readFile28(appDelegatePath, "utf8");
36562
+ const existingStart = appDelegate.indexOf(IOS_PLUGIN_START);
36563
+ const existingEnd = appDelegate.indexOf(IOS_PLUGIN_END);
36564
+ if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
36565
+ throw new TypeError("AbsoluteJS iOS release-readiness markers are malformed.");
36566
+ const source = existingStart < 0 ? `${appDelegate.trimEnd()}
36567
+
36568
+ ${iosPluginRegion3}` : replaceIosPluginRegion(appDelegate, existingStart, existingEnd);
36569
+ let capacitorConfig;
36004
36570
  try {
36005
- await Promise.all([
36006
- writeFile22(join30(staging, "certification.json"), json, {
36007
- flag: "wx"
36008
- }),
36009
- writeFile22(join30(staging, "certification.md"), markdown, {
36010
- flag: "wx"
36011
- })
36012
- ]);
36013
- await rename18(staging, destination).catch(async (error) => {
36014
- if (await exists9(destination))
36015
- return;
36016
- throw error;
36017
- });
36018
- } finally {
36019
- await rm15(staging, { force: true, recursive: true });
36571
+ capacitorConfig = JSON.parse(await readFile28(capacitorConfigPath, "utf8"));
36572
+ } catch {
36573
+ throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting release readiness.");
36020
36574
  }
36021
- await verifyExisting();
36022
- return paths;
36023
- };
36024
- // src/mobile/certificationVerification.ts
36025
- import { access as access18, mkdir as mkdir21, readFile as readFile27, stat as stat6, writeFile as writeFile23 } from "fs/promises";
36026
- import { dirname as dirname20, isAbsolute as isAbsolute10, relative as relative17, resolve as resolve19, sep as sep12 } from "path";
36027
- var ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT = 1;
36028
- var MAX_VERIFICATION_BYTES = 1048576;
36029
- var SHA_PATTERN = /^[a-f0-9]{40}$/u;
36030
- var REPOSITORY_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u;
36031
- var REF_PATTERN = /^refs\/(?:heads|tags)\/[^@\s]+$/u;
36032
- var WORKFLOW_PATTERN = /^\.github\/workflows\/[^/@]+\.ya?ml$/u;
36033
- var isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36034
- var required = (value, name) => {
36035
- if (typeof value !== "string" || value.trim() === "")
36036
- throw new TypeError(`${name} is required.`);
36037
- return value.trim();
36038
- };
36039
- var requireMatch = (value, pattern, name) => {
36040
- if (!pattern.test(value))
36041
- throw new TypeError(`${name} is invalid.`);
36042
- return value;
36043
- };
36044
- var createAbsoluteMobileCertificationVerification = (bundle, environment) => {
36045
- const repository = required(environment.GITHUB_REPOSITORY, "GITHUB_REPOSITORY");
36046
- const workflowReference = required(environment.GITHUB_WORKFLOW_REF, "GITHUB_WORKFLOW_REF");
36047
- const prefix = `${repository}/`;
36048
- const separator = workflowReference.lastIndexOf("@");
36049
- if (!workflowReference.startsWith(prefix) || separator <= prefix.length)
36050
- throw new TypeError("GITHUB_WORKFLOW_REF is invalid.");
36051
- return parseAbsoluteMobileCertificationVerification({
36052
- bundle,
36053
- format: ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
36054
- identity: {
36055
- issuer: "https://token.actions.githubusercontent.com",
36056
- ref: environment.GITHUB_REF,
36057
- repository,
36058
- sha: environment.GITHUB_SHA,
36059
- workflowPath: workflowReference.slice(prefix.length, separator)
36060
- },
36061
- kind: "sigstore-bundle"
36062
- });
36063
- };
36064
- var parseAbsoluteMobileCertificationVerification = (value) => {
36065
- if (!isRecord15(value) || value.format !== ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT || value.kind !== "sigstore-bundle" || !isRecord15(value.bundle) || !isRecord15(value.identity))
36066
- throw new TypeError("Mobile certification verification contract is invalid.");
36067
- const issuer = required(value.identity.issuer, "verification issuer");
36068
- if (issuer !== "https://token.actions.githubusercontent.com")
36069
- throw new TypeError("verification issuer is invalid.");
36070
- const verification = {
36071
- bundle: value.bundle,
36072
- format: ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
36073
- identity: {
36074
- issuer,
36075
- ref: requireMatch(required(value.identity.ref, "verification ref"), REF_PATTERN, "verification ref"),
36076
- repository: requireMatch(required(value.identity.repository, "verification repository"), REPOSITORY_PATTERN, "verification repository"),
36077
- sha: requireMatch(required(value.identity.sha, "verification sha"), SHA_PATTERN, "verification sha"),
36078
- workflowPath: requireMatch(required(value.identity.workflowPath, "verification workflowPath"), WORKFLOW_PATTERN, "verification workflowPath")
36079
- },
36080
- kind: "sigstore-bundle"
36081
- };
36082
- const bytes = new TextEncoder().encode(JSON.stringify(verification));
36083
- if (bytes.byteLength > MAX_VERIFICATION_BYTES)
36084
- throw new TypeError("Mobile certification verification exceeds the 1 MiB limit.");
36085
- return verification;
36086
- };
36087
- var projectFile = (projectRoot, requested) => {
36088
- const root = resolve19(projectRoot);
36089
- const path = resolve19(root, requested);
36090
- const portable = relative17(root, path);
36091
- if (portable === ".." || portable.startsWith(`..${sep12}`) || isAbsolute10(portable))
36092
- throw new TypeError("Mobile certification verification path must remain inside the project.");
36093
- return path;
36094
- };
36095
- var readAbsoluteMobileCertificationVerification = async (projectRoot, requested) => {
36096
- const path = projectFile(projectRoot, requested);
36097
- await access18(path).catch(() => {
36098
- throw new TypeError(`Mobile certification verification does not exist: ${path}`);
36099
- });
36100
- const metadata = await stat6(path);
36101
- if (!metadata.isFile() || metadata.size > MAX_VERIFICATION_BYTES)
36102
- throw new TypeError("Mobile certification verification file is invalid.");
36103
- return parseAbsoluteMobileCertificationVerification(JSON.parse(await readFile27(path, "utf8")));
36575
+ if (typeof capacitorConfig !== "object" || capacitorConfig === null)
36576
+ throw new TypeError("iOS capacitor.config.json must contain an object.");
36577
+ const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
36578
+ if (!Array.isArray(packageClassList))
36579
+ throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting release readiness.");
36580
+ const classes = packageClassList.filter((value) => typeof value === "string");
36581
+ const nextConfig = `${JSON.stringify({
36582
+ ...capacitorConfig,
36583
+ packageClassList: [
36584
+ ...new Set([...classes, "AbsoluteReleaseReadinessPlugin"])
36585
+ ]
36586
+ }, null, "\t")}
36587
+ `;
36588
+ const changed = await Promise.all([
36589
+ writeChanged3(appDelegatePath, source),
36590
+ writeChanged3(capacitorConfigPath, nextConfig)
36591
+ ]);
36592
+ return changed.some(Boolean);
36104
36593
  };
36105
- var readAbsoluteSigstoreBundle = async (projectRoot, requested) => {
36106
- const path = projectFile(projectRoot, requested);
36107
- const metadata = await stat6(path).catch(() => null);
36108
- if (!metadata?.isFile() || metadata.size > MAX_VERIFICATION_BYTES)
36109
- throw new TypeError("Sigstore bundle file is invalid.");
36110
- const value = JSON.parse(await readFile27(path, "utf8"));
36111
- if (!isRecord15(value))
36112
- throw new TypeError("Sigstore bundle is invalid.");
36113
- return value;
36594
+ var configureAndroid5 = async (config) => {
36595
+ const mainActivityPath = await activityPath(config);
36596
+ const activity = await readFile28(mainActivityPath, "utf8");
36597
+ const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
36598
+ if (!packageName)
36599
+ throw new TypeError("Android MainActivity package declaration was not found.");
36600
+ const pluginPath = join31(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN3);
36601
+ const changed = await Promise.all([
36602
+ writeChanged3(mainActivityPath, injectRegistration(activity)),
36603
+ writeChanged3(pluginPath, pluginSource(packageName))
36604
+ ]);
36605
+ return changed.some(Boolean);
36114
36606
  };
36115
- var writeAbsoluteMobileCertificationVerification = async (projectRoot, requested, verification) => {
36116
- const path = projectFile(projectRoot, requested);
36117
- const parsed = parseAbsoluteMobileCertificationVerification(verification);
36118
- await mkdir21(dirname20(path), { recursive: true });
36119
- await writeFile23(path, `${JSON.stringify(parsed, null, 2)}
36120
- `, {
36121
- mode: 384
36122
- });
36123
- return path;
36607
+ var applyAbsoluteNativeReleaseReadiness = async (config, platforms = config.platforms) => {
36608
+ if (config.engine !== "capacitor")
36609
+ return { changed: false };
36610
+ const changed = await Promise.all([
36611
+ platforms.includes("android") ? configureAndroid5(config) : false,
36612
+ platforms.includes("ios") ? configureIos5(config) : false
36613
+ ]);
36614
+ return { changed: changed.some(Boolean) };
36124
36615
  };
36616
+
36617
+ // src/mobile/index.ts
36618
+ init_pageProtocol();
36619
+ init_producerContext();
36620
+ init_releaseArtifact();
36621
+
36125
36622
  // src/mobile/releasePublisher.ts
36126
- import { access as access19 } from "fs/promises";
36127
- import { isAbsolute as isAbsolute11, relative as relative18, resolve as resolve20, sep as sep13 } from "path";
36623
+ import { access as access20 } from "fs/promises";
36624
+ import { isAbsolute as isAbsolute12, relative as relative19, resolve as resolve21, sep as sep14 } from "path";
36128
36625
  import { pathToFileURL as pathToFileURL3 } from "url";
36129
36626
  var prepareAbsoluteIosRelease = async (publisher, options) => {
36130
36627
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -36158,24 +36655,24 @@ var requireCertificationReceipt = (publication, certification, requirement, rele
36158
36655
  if (verification && (!receipt.provenance || receipt.provenance.subject !== releaseId))
36159
36656
  throw new TypeError("Native release registry did not retain trusted certification provenance.");
36160
36657
  };
36161
- var isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36162
- var isPublisher = (value) => isRecord16(value) && typeof value.publish === "function";
36658
+ var isRecord17 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36659
+ var isPublisher = (value) => isRecord17(value) && typeof value.publish === "function";
36163
36660
  var publisherModulePath = (projectRoot, requested) => {
36164
- const root = resolve20(projectRoot);
36165
- const path = resolve20(root, requested);
36166
- const projectRelative = relative18(root, path);
36167
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep13}`) || isAbsolute11(projectRelative)) {
36661
+ const root = resolve21(projectRoot);
36662
+ const path = resolve21(root, requested);
36663
+ const projectRelative = relative19(root, path);
36664
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep14}`) || isAbsolute12(projectRelative)) {
36168
36665
  throw new TypeError("mobile publish --registry must remain inside the project.");
36169
36666
  }
36170
36667
  return path;
36171
36668
  };
36172
36669
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
36173
36670
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
36174
- await access19(modulePath).catch(() => {
36671
+ await access20(modulePath).catch(() => {
36175
36672
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
36176
36673
  });
36177
36674
  const loaded = await import(pathToFileURL3(modulePath).href);
36178
- const publisher = isRecord16(loaded) ? loaded.default ?? loaded.registry : undefined;
36675
+ const publisher = isRecord17(loaded) ? loaded.default ?? loaded.registry : undefined;
36179
36676
  if (!isPublisher(publisher)) {
36180
36677
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
36181
36678
  }
@@ -36257,7 +36754,7 @@ var publishAbsoluteIosRelease = async (options) => {
36257
36754
  init_pageProtocol();
36258
36755
  init_releaseArtifact();
36259
36756
  import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
36260
- import { dirname as dirname21, extname as extname6, relative as relative19, resolve as resolve21 } from "path";
36757
+ import { dirname as dirname22, extname as extname6, relative as relative20, resolve as resolve22 } from "path";
36261
36758
  import ts2 from "typescript";
36262
36759
  var ROUTE_METHODS = new Set(["get", "head"]);
36263
36760
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
@@ -36308,7 +36805,7 @@ var PAGE_HANDLERS = new Map([
36308
36805
  ]
36309
36806
  ]);
36310
36807
  var posixPath = (value) => value.replace(/\\/g, "/");
36311
- var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname21(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
36808
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname22(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
36312
36809
  var createProgram = (entry, projectRoot) => {
36313
36810
  const configPath2 = findTsconfig(entry, projectRoot);
36314
36811
  if (!configPath2) {
@@ -36320,7 +36817,7 @@ var createProgram = (entry, projectRoot) => {
36320
36817
  target: ts2.ScriptTarget.ESNext
36321
36818
  });
36322
36819
  }
36323
- const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname21(configPath2));
36820
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname22(configPath2));
36324
36821
  if (!parsed.fileNames.includes(entry))
36325
36822
  parsed.fileNames.push(entry);
36326
36823
  return ts2.createProgram(parsed.fileNames, parsed.options);
@@ -36426,7 +36923,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
36426
36923
  const declaration = symbol?.declarations?.[0];
36427
36924
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
36428
36925
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
36429
- const source = posixPath(relative19(projectRoot, file));
36926
+ const source = posixPath(relative20(projectRoot, file));
36430
36927
  return `${source}#${exportedName}`;
36431
36928
  };
36432
36929
  var resolveAlias = (symbol, checker) => {
@@ -36665,7 +37162,7 @@ var analyzeProgram = (program, projectRoot) => {
36665
37162
  const checker = program.getTypeChecker();
36666
37163
  const analyzed = new Map;
36667
37164
  for (const sourceFile of program.getSourceFiles()) {
36668
- const resolvedFile = resolve21(sourceFile.fileName);
37165
+ const resolvedFile = resolve22(sourceFile.fileName);
36669
37166
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
36670
37167
  continue;
36671
37168
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -36758,14 +37255,14 @@ var transformFile = (source, fileName, analysis) => {
36758
37255
  };
36759
37256
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
36760
37257
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
36761
- const projectRoot = resolve21(options.projectRoot ?? process.cwd());
36762
- const entry = resolve21(options.entry);
37258
+ const projectRoot = resolve22(options.projectRoot ?? process.cwd());
37259
+ const entry = resolve22(options.entry);
36763
37260
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
36764
37261
  return {
36765
37262
  name: "absolute-mobile-route-metadata",
36766
37263
  setup(build) {
36767
37264
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
36768
- const analysis = analyzed.get(resolve21(path));
37265
+ const analysis = analyzed.get(resolve22(path));
36769
37266
  if (!analysis)
36770
37267
  return;
36771
37268
  const source = await Bun.file(path).text();
@@ -36778,11 +37275,11 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
36778
37275
  };
36779
37276
  };
36780
37277
  var inspectAbsoluteMobileRouteMetadata = (options) => {
36781
- const projectRoot = resolve21(options.projectRoot ?? process.cwd());
36782
- const entry = resolve21(options.entry);
37278
+ const projectRoot = resolve22(options.projectRoot ?? process.cwd());
37279
+ const entry = resolve22(options.entry);
36783
37280
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
36784
37281
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
36785
- file: posixPath(relative19(projectRoot, file)),
37282
+ file: posixPath(relative20(projectRoot, file)),
36786
37283
  metadata
36787
37284
  })));
36788
37285
  };
@@ -37116,7 +37613,7 @@ var createCapacitor = (win) => {
37116
37613
  callbackId
37117
37614
  }, callback);
37118
37615
  };
37119
- const p = new Promise((resolve22) => call.then(() => resolve22({ remove })));
37616
+ const p = new Promise((resolve23) => call.then(() => resolve23({ remove })));
37120
37617
  p.remove = async () => {
37121
37618
  console.warn(`Using addListener() without 'await' is deprecated.`);
37122
37619
  await remove();
@@ -37321,11 +37818,11 @@ class CapacitorCookiesPluginWeb extends WebPlugin {
37321
37818
  var CapacitorCookies = registerPlugin("CapacitorCookies", {
37322
37819
  web: () => new CapacitorCookiesPluginWeb
37323
37820
  });
37324
- var readBlobAsBase64 = async (blob) => new Promise((resolve22, reject) => {
37821
+ var readBlobAsBase64 = async (blob) => new Promise((resolve23, reject) => {
37325
37822
  const reader = new FileReader;
37326
37823
  reader.onload = () => {
37327
37824
  const base64String = reader.result;
37328
- resolve22(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
37825
+ resolve23(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
37329
37826
  };
37330
37827
  reader.onerror = (error) => reject(error);
37331
37828
  reader.readAsDataURL(blob);
@@ -37501,7 +37998,7 @@ var REPORT_KINDS = new Set([
37501
37998
  "signal"
37502
37999
  ]);
37503
38000
  var nativeObservability = registerPlugin("AbsoluteMobileObservability");
37504
- var isRecord17 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
38001
+ var isRecord18 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
37505
38002
  var redactText = (value) => value.slice(0, MAX_STRING_LENGTH).replace(/\b(Bearer)\s+[A-Za-z0-9._~+/-]+=*/giu, "$1 [REDACTED]").replace(/([?&](?:access_token|api_?key|authorization|code|id_token|password|refresh_token|secret|token)=)[^&#\s]*/giu, "$1[REDACTED]").replace(/\b((?:access_?token|api_?key|authorization|client_?secret|cookie|id_?token|password|passwd|refresh_?token|secret|token)\s*[:=]\s*)[^,\s;]+/giu, "$1[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu, "[REDACTED]");
37506
38003
  var redactValue = (value, depth = 0) => {
37507
38004
  if (depth >= MAX_REDACTION_DEPTH)
@@ -37512,7 +38009,7 @@ var redactValue = (value, depth = 0) => {
37512
38009
  return value;
37513
38010
  if (Array.isArray(value))
37514
38011
  return value.slice(0, 64).map((entry) => redactValue(entry, depth + 1));
37515
- if (!isRecord17(value))
38012
+ if (!isRecord18(value))
37516
38013
  return String(value).slice(0, MAX_STRING_LENGTH);
37517
38014
  return Object.fromEntries(Object.entries(value).slice(0, 128).map(([key, entry]) => [
37518
38015
  key,
@@ -37520,7 +38017,7 @@ var redactValue = (value, depth = 0) => {
37520
38017
  ]));
37521
38018
  };
37522
38019
  var parseReport = (value) => {
37523
- if (!isRecord17(value) || !isRecord17(value.details))
38020
+ if (!isRecord18(value) || !isRecord18(value.details))
37524
38021
  return;
37525
38022
  if (typeof value.id !== "string" || !REPORT_ID.test(value.id))
37526
38023
  return;
@@ -37531,7 +38028,7 @@ var parseReport = (value) => {
37531
38028
  if (value.platform !== "android" && value.platform !== "ios")
37532
38029
  return;
37533
38030
  const details = redactValue(value.details);
37534
- if (!isRecord17(details))
38031
+ if (!isRecord18(details))
37535
38032
  return;
37536
38033
  if (new TextEncoder().encode(JSON.stringify(details)).byteLength > MAX_DETAIL_BYTES)
37537
38034
  return;
@@ -41985,8 +42482,8 @@ var createBeacon = (options) => {
41985
42482
  if (await fontAvailable(element))
41986
42483
  return;
41987
42484
  if (confirmationMs > 0) {
41988
- await new Promise((resolve22) => {
41989
- const timer2 = window.setTimeout(resolve22, confirmationMs);
42485
+ await new Promise((resolve23) => {
42486
+ const timer2 = window.setTimeout(resolve23, confirmationMs);
41990
42487
  cleanups.push(() => window.clearTimeout(timer2));
41991
42488
  });
41992
42489
  if (document.visibilityState === "hidden")
@@ -42974,51 +43471,51 @@ var createAbsoluteMobileUpdateClient = (options) => {
42974
43471
  };
42975
43472
  };
42976
43473
  // src/mobile/updatePublisher.ts
42977
- import { access as access20 } from "fs/promises";
42978
- import { isAbsolute as isAbsolute12, relative as relative21, resolve as resolve23, sep as sep14 } from "path";
43474
+ import { access as access21 } from "fs/promises";
43475
+ import { isAbsolute as isAbsolute13, relative as relative22, resolve as resolve24, sep as sep15 } from "path";
42979
43476
  import { pathToFileURL as pathToFileURL4 } from "url";
42980
43477
 
42981
43478
  // src/mobile/updateSigning.ts
42982
43479
  import { createHash as createHash17, sign, verify } from "crypto";
42983
43480
  import {
42984
43481
  cp as cp2,
42985
- mkdir as mkdir22,
42986
- mkdtemp as mkdtemp11,
42987
- readdir as readdir10,
42988
- readFile as readFile28,
42989
- rename as rename19,
42990
- rm as rm16,
42991
- stat as stat7,
42992
- writeFile as writeFile24
43482
+ mkdir as mkdir23,
43483
+ mkdtemp as mkdtemp12,
43484
+ readdir as readdir11,
43485
+ readFile as readFile29,
43486
+ rename as rename20,
43487
+ rm as rm17,
43488
+ stat as stat8,
43489
+ writeFile as writeFile25
42993
43490
  } from "fs/promises";
42994
- import { dirname as dirname22, join as join31, relative as relative20, resolve as resolve22 } from "path";
43491
+ import { dirname as dirname23, join as join32, relative as relative21, resolve as resolve23 } from "path";
42995
43492
  var UPDATE_MANIFEST_FILE = "update.json";
42996
43493
  var UPDATE_FILES_DIRECTORY = "files";
42997
43494
  var sha2565 = (value) => createHash17("sha256").update(value).digest("hex");
42998
43495
  var listFiles = async (root, directory = root) => {
42999
- const entries = await readdir10(directory, { withFileTypes: true });
43496
+ const entries = await readdir11(directory, { withFileTypes: true });
43000
43497
  const paths = await Promise.all(entries.map(async (entry) => {
43001
- const path = join31(directory, entry.name);
43498
+ const path = join32(directory, entry.name);
43002
43499
  if (entry.isDirectory())
43003
43500
  return listFiles(root, path);
43004
43501
  if (!entry.isFile())
43005
43502
  throw new TypeError("Mobile updates cannot contain links or special files.");
43006
- return [relative20(root, path).replaceAll("\\", "/")];
43503
+ return [relative21(root, path).replaceAll("\\", "/")];
43007
43504
  }));
43008
43505
  return paths.flat().sort((left, right) => left.localeCompare(right));
43009
43506
  };
43010
43507
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
43011
- const source = join31(root, path);
43508
+ const source = join32(root, path);
43012
43509
  const [metadata, contents] = await Promise.all([
43013
- stat7(source),
43014
- readFile28(source)
43510
+ stat8(source),
43511
+ readFile29(source)
43015
43512
  ]);
43016
43513
  return { bytes: metadata.size, path, sha256: sha2565(contents) };
43017
43514
  }));
43018
43515
  var releaseIdFor = (value) => `amu_${sha2565(canonicalizeAbsoluteMobileUpdate(value))}`;
43019
43516
  var buildAbsoluteMobileUpdate = async (options) => {
43020
- const bundleDirectory = resolve22(options.bundleDirectory);
43021
- const outputRoot = resolve22(options.outputDirectory);
43517
+ const bundleDirectory = resolve23(options.bundleDirectory);
43518
+ const outputRoot = resolve23(options.outputDirectory);
43022
43519
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
43023
43520
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
43024
43521
  const paths = await listFiles(bundleDirectory);
@@ -43046,38 +43543,38 @@ var buildAbsoluteMobileUpdate = async (options) => {
43046
43543
  value: signature.toString("base64")
43047
43544
  }
43048
43545
  });
43049
- await mkdir22(outputRoot, { recursive: true });
43050
- const outputDirectory = join31(outputRoot, manifest.releaseId);
43051
- const staging = await mkdtemp11(join31(outputRoot, ".stage-"));
43546
+ await mkdir23(outputRoot, { recursive: true });
43547
+ const outputDirectory = join32(outputRoot, manifest.releaseId);
43548
+ const staging = await mkdtemp12(join32(outputRoot, ".stage-"));
43052
43549
  try {
43053
- await cp2(bundleDirectory, join31(staging, UPDATE_FILES_DIRECTORY), {
43550
+ await cp2(bundleDirectory, join32(staging, UPDATE_FILES_DIRECTORY), {
43054
43551
  force: true,
43055
43552
  recursive: true
43056
43553
  });
43057
- await writeFile24(join31(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
43554
+ await writeFile25(join32(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
43058
43555
  `);
43059
- await rename19(staging, outputDirectory);
43556
+ await rename20(staging, outputDirectory);
43060
43557
  } catch (error) {
43061
- await rm16(staging, { force: true, recursive: true });
43558
+ await rm17(staging, { force: true, recursive: true });
43062
43559
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
43063
43560
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
43064
43561
  throw error;
43065
43562
  }
43066
43563
  return {
43067
43564
  manifest,
43068
- manifestPath: join31(outputDirectory, UPDATE_MANIFEST_FILE),
43565
+ manifestPath: join32(outputDirectory, UPDATE_MANIFEST_FILE),
43069
43566
  outputDirectory
43070
43567
  };
43071
43568
  };
43072
43569
  var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) => {
43073
- const source = resolve22(updateDirectory, UPDATE_FILES_DIRECTORY, path);
43074
- const root = resolve22(updateDirectory, UPDATE_FILES_DIRECTORY);
43570
+ const source = resolve23(updateDirectory, UPDATE_FILES_DIRECTORY, path);
43571
+ const root = resolve23(updateDirectory, UPDATE_FILES_DIRECTORY);
43075
43572
  if (!source.startsWith(`${root}/`))
43076
43573
  throw new TypeError("Update file escaped its release.");
43077
- await mkdir22(dirname22(destination), { recursive: true });
43574
+ await mkdir23(dirname23(destination), { recursive: true });
43078
43575
  await cp2(source, destination, { force: true });
43079
43576
  };
43080
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile28(join31(resolve22(directory), UPDATE_MANIFEST_FILE), "utf8")));
43577
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile29(join32(resolve23(directory), UPDATE_MANIFEST_FILE), "utf8")));
43081
43578
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
43082
43579
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
43083
43580
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -43193,17 +43690,17 @@ var pruneAbsoluteMobileUpdates = async (options) => {
43193
43690
  };
43194
43691
  var object5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
43195
43692
  var isPublisher2 = (value) => object5(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
43196
- var projectPath2 = (projectRoot, requested, label) => {
43197
- const root = resolve23(projectRoot);
43198
- const path = resolve23(root, requested);
43199
- const projectRelative = relative21(root, path);
43200
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep14}`) || isAbsolute12(projectRelative))
43693
+ var projectPath3 = (projectRoot, requested, label) => {
43694
+ const root = resolve24(projectRoot);
43695
+ const path = resolve24(root, requested);
43696
+ const projectRelative = relative22(root, path);
43697
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep15}`) || isAbsolute13(projectRelative))
43201
43698
  throw new TypeError(`${label} must remain inside the project.`);
43202
43699
  return path;
43203
43700
  };
43204
43701
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
43205
- const modulePath = projectPath2(projectRoot, requestedModulePath, "mobile update registry");
43206
- await access20(modulePath).catch(() => {
43702
+ const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
43703
+ await access21(modulePath).catch(() => {
43207
43704
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
43208
43705
  });
43209
43706
  const loaded = await import(pathToFileURL4(modulePath).href);
@@ -43225,7 +43722,7 @@ var promoteAbsoluteMobileUpdate = async (options) => {
43225
43722
  return result;
43226
43723
  };
43227
43724
  var publishAbsoluteMobileUpdate = async (options) => {
43228
- const releaseDirectory = projectPath2(options.projectRoot, options.releaseDirectory, "mobile update release directory");
43725
+ const releaseDirectory = projectPath3(options.projectRoot, options.releaseDirectory, "mobile update release directory");
43229
43726
  const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
43230
43727
  const result = await options.publisher.publishUpdate({
43231
43728
  manifest,
@@ -43310,6 +43807,8 @@ export {
43310
43807
  ABSOLUTE_IOS_RELEASE_FORMAT,
43311
43808
  ABSOLUTE_IOS_SIMULATOR_NAME,
43312
43809
  ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
43810
+ ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
43811
+ ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
43313
43812
  ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
43314
43813
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
43315
43814
  ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
@@ -43373,6 +43872,7 @@ export {
43373
43872
  assertAbsoluteAndroidInstalledAppUpgrade,
43374
43873
  assertAbsoluteDeviceCapabilityPackages,
43375
43874
  assertAbsoluteIosInstalledAppUpgrade,
43875
+ auditAbsoluteMobileCiPromotion,
43376
43876
  buildAbsoluteAndroidRelease,
43377
43877
  buildAbsoluteExpoAndroidWslBundle,
43378
43878
  buildAbsoluteIosRelease,
@@ -43414,6 +43914,7 @@ export {
43414
43914
  createAbsoluteRemoteIosDevProject,
43415
43915
  directAbsoluteProjectPackages,
43416
43916
  discoverAbsoluteDeviceCapabilities,
43917
+ discoverAbsoluteMobilePromotionRun,
43417
43918
  discoverAbsoluteSyncSchema,
43418
43919
  disposeAbsoluteMobilePage,
43419
43920
  drainAbsoluteMobileNativeObservability,
@@ -43436,6 +43937,7 @@ export {
43436
43937
  inspectAbsoluteAndroidInstalledApp,
43437
43938
  inspectAbsoluteBundletool,
43438
43939
  inspectAbsoluteIosInstalledApp,
43940
+ inspectAbsoluteMobileCiRun,
43439
43941
  inspectAbsoluteMobileRouteMetadata,
43440
43942
  inspectAbsoluteMobileUpdateHealth,
43441
43943
  inspectAbsoluteMobileUpdateRollout,
@@ -43488,9 +43990,12 @@ export {
43488
43990
  parseAbsoluteIosLogLine,
43489
43991
  parseAbsoluteMobileBuildPageMetadata,
43490
43992
  parseAbsoluteMobileCertificationVerification,
43993
+ parseAbsoluteMobileCiRunStatus,
43491
43994
  parseAbsoluteMobileCompatibilityArtifact,
43492
43995
  parseAbsoluteMobilePageEnvelope,
43493
43996
  parseAbsoluteMobilePageRequest,
43997
+ parseAbsoluteMobilePromotionContext,
43998
+ parseAbsoluteMobilePromotionRunList,
43494
43999
  parseAbsoluteMobileUnsignedUpdateManifest,
43495
44000
  parseAbsoluteMobileUpdateManifest,
43496
44001
  parseAbsoluteMobileUpdateRequest,
@@ -43567,6 +44072,7 @@ export {
43567
44072
  verifyAbsoluteMobileReleaseCertification,
43568
44073
  verifyAbsoluteMobileUpdateSignature,
43569
44074
  waitForAbsoluteIosHmrLog,
44075
+ watchAbsoluteMobileCiRun,
43570
44076
  writeAbsoluteCapacitorConfig,
43571
44077
  writeAbsoluteExpoProject,
43572
44078
  writeAbsoluteMobileCertificationVerification,
@@ -43575,5 +44081,5 @@ export {
43575
44081
  writeAbsoluteMobileUpdateRegistry
43576
44082
  };
43577
44083
 
43578
- //# debugId=F05777F8BA048C6164756E2164756E21
44084
+ //# debugId=199363435B745D2564756E2164756E21
43579
44085
  //# sourceMappingURL=index.js.map