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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,1057 @@ 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 = 1;
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 isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
33570
+ var requiredString = (value, name) => {
33571
+ if (typeof value !== "string" || value.trim() === "")
33572
+ throw new TypeError(`${name} is invalid.`);
33573
+ return value.trim();
33574
+ };
33575
+ var optionalString = (value, name) => {
33576
+ if (value === null || value === undefined)
33577
+ return null;
33578
+ return requiredString(value, name);
33579
+ };
33580
+ var runId = (value, name) => {
33581
+ if (!RUN_ID_PATTERN.test(value))
33582
+ throw new TypeError(`${name} is invalid.`);
33583
+ return value;
33584
+ };
33585
+ var repositoryArgs = (repository) => {
33586
+ if (repository === undefined)
33587
+ return [];
33588
+ if (!REPOSITORY_PATTERN2.test(repository))
33589
+ throw new TypeError("GitHub repository must be owner/name.");
33590
+ return ["--repo", repository];
33591
+ };
33592
+ var projectPath = (projectRoot, requested) => {
33593
+ const root = resolve19(projectRoot);
33594
+ const path = resolve19(root, requested);
33595
+ const portable = relative17(root, path);
33596
+ if (portable === ".." || portable.startsWith(`..${sep12}`) || isAbsolute11(portable))
33597
+ throw new TypeError("Mobile CI audit output must remain inside the project.");
33598
+ return path;
33599
+ };
33600
+ var defaultRunner = async (command, options) => {
33601
+ const child = Bun.spawn(command, {
33602
+ cwd: options.cwd,
33603
+ env: options.env,
33604
+ stderr: "pipe",
33605
+ stdin: "ignore",
33606
+ stdout: "pipe"
33607
+ });
33608
+ const [exitCode, stderr, stdout] = await Promise.all([
33609
+ child.exited,
33610
+ new Response(child.stderr).text(),
33611
+ new Response(child.stdout).text()
33612
+ ]);
33613
+ return { exitCode, stderr, stdout };
33614
+ };
33615
+ var requireCommand = async (run, command, options, label) => {
33616
+ const result = await run(command, options);
33617
+ if (result.exitCode !== 0)
33618
+ throw new TypeError(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
33619
+ if (result.stdout.length > MAX_GITHUB_OUTPUT_BYTES || result.stderr.length > MAX_GITHUB_OUTPUT_BYTES)
33620
+ throw new TypeError(`${label} output exceeded the 1 MiB limit.`);
33621
+ return result;
33622
+ };
33623
+ var parseJob = (value) => {
33624
+ if (!isRecord13(value))
33625
+ throw new TypeError("GitHub run job is invalid.");
33626
+ return {
33627
+ conclusion: optionalString(value.conclusion, "GitHub job conclusion"),
33628
+ name: requiredString(value.name, "GitHub job name"),
33629
+ status: requiredString(value.status, "GitHub job status")
33630
+ };
33631
+ };
33632
+ var inspectAbsoluteMobileCiRun = async (options) => {
33633
+ const requestedRunId = runId(options.runId, "GitHub run ID");
33634
+ const result = await requireCommand(options.run ?? defaultRunner, [
33635
+ "gh",
33636
+ "run",
33637
+ "view",
33638
+ requestedRunId,
33639
+ ...repositoryArgs(options.repository),
33640
+ "--json",
33641
+ "conclusion,createdAt,databaseId,headBranch,headSha,jobs,startedAt,status,updatedAt,url,workflowName"
33642
+ ], { cwd: options.projectRoot }, "GitHub workflow inspection");
33643
+ return parseAbsoluteMobileCiRunStatus(JSON.parse(result.stdout), requestedRunId);
33644
+ };
33645
+ var parseAbsoluteMobileCiRunStatus = (value, requestedRunId) => {
33646
+ if (!isRecord13(value) || !Array.isArray(value.jobs))
33647
+ throw new TypeError("GitHub workflow run response is invalid.");
33648
+ const { databaseId } = value;
33649
+ if (typeof databaseId !== "number" && typeof databaseId !== "string" || String(databaseId) !== requestedRunId)
33650
+ throw new TypeError("GitHub workflow run identity does not match.");
33651
+ return {
33652
+ conclusion: optionalString(value.conclusion, "GitHub run conclusion"),
33653
+ createdAt: requiredString(value.createdAt, "GitHub run createdAt"),
33654
+ headBranch: requiredString(value.headBranch, "GitHub run headBranch"),
33655
+ headSha: requiredString(value.headSha, "GitHub run headSha"),
33656
+ jobs: value.jobs.map(parseJob),
33657
+ runId: requestedRunId,
33658
+ startedAt: optionalString(value.startedAt, "GitHub run startedAt"),
33659
+ status: requiredString(value.status, "GitHub run status"),
33660
+ updatedAt: requiredString(value.updatedAt, "GitHub run updatedAt"),
33661
+ url: requiredString(value.url, "GitHub run URL"),
33662
+ workflowName: requiredString(value.workflowName, "GitHub workflow name")
33663
+ };
33664
+ };
33665
+ var parseAbsoluteMobilePromotionContext = (value) => {
33666
+ if (!isRecord13(value) || value.format !== ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT || value.platform !== "android" && value.platform !== "ios")
33667
+ throw new TypeError("Mobile promotion context is invalid.");
33668
+ const { platform: platform3 } = value;
33669
+ const sourceArtifact = requiredString(value.sourceArtifact, "promotion source artifact");
33670
+ if (sourceArtifact !== `absolute-mobile-${platform3}`)
33671
+ throw new TypeError("Mobile promotion source artifact is invalid.");
33672
+ return {
33673
+ format: ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
33674
+ platform: platform3,
33675
+ promotionRunId: runId(requiredString(value.promotionRunId, "promotion run ID"), "promotion run ID"),
33676
+ sourceArtifact,
33677
+ sourceRunId: runId(requiredString(value.sourceRunId, "source run ID"), "source run ID")
33678
+ };
33679
+ };
33680
+ var watchAbsoluteMobileCiRun = async (options) => {
33681
+ const requestedRunId = runId(options.runId, "GitHub run ID");
33682
+ await requireCommand(options.run ?? defaultRunner, [
33683
+ "gh",
33684
+ "run",
33685
+ "watch",
33686
+ requestedRunId,
33687
+ ...repositoryArgs(options.repository),
33688
+ "--exit-status"
33689
+ ], { cwd: options.projectRoot }, "GitHub workflow watch");
33690
+ return inspectAbsoluteMobileCiRun({ ...options, runId: requestedRunId });
33691
+ };
33692
+ var readBoundedJson = async (path, label) => {
33693
+ const metadata = await stat7(path).catch(() => null);
33694
+ if (!metadata?.isFile() || metadata.size > MAX_AUDIT_JSON_BYTES)
33695
+ throw new TypeError(`${label} is missing or exceeds the 1 MiB limit.`);
33696
+ const value = JSON.parse(await readFile22(path, "utf8"));
33697
+ return value;
33698
+ };
33699
+ var findFiles = async (root, name) => {
33700
+ const found = [];
33701
+ let visited = 0;
33702
+ const walk = async (directory) => {
33703
+ const entries = await readdir7(directory, { withFileTypes: true });
33704
+ await Promise.all(entries.map(async (entry) => {
33705
+ visited += 1;
33706
+ if (visited > MAX_SEARCH_FILES)
33707
+ throw new TypeError("Downloaded GitHub artifact contains too many files.");
33708
+ const path = join24(directory, entry.name);
33709
+ if (entry.isDirectory()) {
33710
+ await walk(path);
33711
+ return;
33712
+ }
33713
+ if (entry.isFile() && entry.name === name)
33714
+ found.push(path);
33715
+ }));
33716
+ };
33717
+ await walk(root);
33718
+ return found;
33719
+ };
33720
+ var inferPromotionPlatform = (status) => {
33721
+ const matched = status.jobs.flatMap((job) => {
33722
+ if (job.name === "Promote exact certified Android release")
33723
+ return ["android"];
33724
+ if (job.name === "Promote exact certified iOS release")
33725
+ return ["ios"];
33726
+ return [];
33727
+ });
33728
+ if (matched.length !== 1)
33729
+ throw new TypeError("GitHub run does not contain exactly one mobile promotion job.");
33730
+ const [platform3] = matched;
33731
+ if (!platform3)
33732
+ throw new TypeError("Mobile promotion platform is missing.");
33733
+ return platform3;
33734
+ };
33735
+ var onlyPath = (paths, label) => {
33736
+ const [path] = paths;
33737
+ if (paths.length !== 1 || !path)
33738
+ throw new TypeError(`${label} must contain exactly one matching file.`);
33739
+ return path;
33740
+ };
33741
+ var canonicalJson2 = (value) => {
33742
+ if (Array.isArray(value))
33743
+ return `[${value.map((entry) => canonicalJson2(entry)).join(",")}]`;
33744
+ if (isRecord13(value))
33745
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
33746
+ return JSON.stringify(value);
33747
+ };
33748
+ var publicationSummary = (value, platform3, release, certification) => {
33749
+ if (!isRecord13(value) || !isRecord13(value.record) || !isRecord13(value.record.metadata))
33750
+ throw new TypeError("Mobile promotion receipt is invalid.");
33751
+ const { metadata } = value.record;
33752
+ if (metadata.appId !== release.metadata.appId || metadata.releaseId !== release.metadata.releaseId || metadata.sha256 !== release.metadata.sha256)
33753
+ throw new TypeError("Mobile promotion receipt release identity does not match.");
33754
+ if (!isRecord13(value.certification) || !isRecord13(value.certification.provenance))
33755
+ throw new TypeError("Mobile promotion receipt lacks trusted certification provenance.");
33756
+ 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)
33757
+ throw new TypeError("Mobile promotion certification receipt does not match.");
33758
+ requiredString(value.certification.provenance.issuer, "promotion provenance issuer");
33759
+ requiredString(value.certification.provenance.verificationId, "promotion provenance verification ID");
33760
+ const provenanceTime = requiredString(value.certification.provenance.verifiedAt, "promotion provenance verification time");
33761
+ if (Number.isNaN(Date.parse(provenanceTime)))
33762
+ throw new TypeError("Mobile promotion provenance time is invalid.");
33763
+ if (typeof value.reused !== "boolean")
33764
+ throw new TypeError("Mobile promotion reuse status is invalid.");
33765
+ const channel = isRecord13(value.channel) ? optionalString(value.channel.channel, "promotion channel") : null;
33766
+ if (isRecord13(value.channel) && value.channel.releaseId !== release.metadata.releaseId)
33767
+ throw new TypeError("Mobile promotion channel receipt does not match.");
33768
+ const providerReceipt = platform3 === "android" ? value.googlePlay : value.appStoreConnect;
33769
+ if (providerReceipt === undefined)
33770
+ return {
33771
+ channel,
33772
+ provider: "registry",
33773
+ reused: value.reused,
33774
+ stage: null
33775
+ };
33776
+ if (!isRecord13(providerReceipt) || !isRecord13(providerReceipt.receipt))
33777
+ throw new TypeError("Mobile store promotion receipt is invalid.");
33778
+ if (typeof providerReceipt.reused !== "boolean")
33779
+ throw new TypeError("Mobile store promotion reuse status is invalid.");
33780
+ const expectedProvider = platform3 === "android" ? "google-play" : "app-store-connect";
33781
+ if (providerReceipt.receipt.provider !== expectedProvider || providerReceipt.receipt.releaseId !== release.metadata.releaseId || providerReceipt.receipt.sha256 !== release.metadata.sha256)
33782
+ throw new TypeError("Mobile store promotion receipt does not match.");
33783
+ return {
33784
+ channel,
33785
+ provider: expectedProvider,
33786
+ reused: providerReceipt.reused,
33787
+ stage: requiredString(providerReceipt.receipt.stage, "Mobile store promotion stage")
33788
+ };
33789
+ };
33790
+ var renderAuditMarkdown = (audit) => `# AbsoluteJS mobile promotion audit
33791
+
33792
+ - Status: **VERIFIED**
33793
+ - Platform: ${audit.platform}
33794
+ - Release: \`${audit.release.releaseId}\`
33795
+ - Artifact SHA-256: \`${audit.release.sha256}\`
33796
+ - Source workflow run: ${audit.github.sourceRunId}
33797
+ - Promotion workflow run: ${audit.github.promotionRunId}
33798
+ - Workflow: ${audit.github.workflowName}
33799
+ - GitHub identity: \`${audit.provenance.repository}/${audit.provenance.workflowPath}@${audit.provenance.ref}\`
33800
+ - Certification: \`${audit.certification.certificationId}\` (${audit.certification.strength})
33801
+ - Publication: ${audit.publication.provider}${audit.publication.stage ? ` / ${audit.publication.stage}` : ""}
33802
+ - Verified at: ${audit.verifiedAt}
33803
+
33804
+ This report contains no credentials, device identifiers, local absolute paths, application data, or GitHub token.
33805
+ `;
33806
+ var auditAbsoluteMobileCiPromotion = async (options) => {
33807
+ const runner = options.run ?? defaultRunner;
33808
+ const promotionRunId = runId(options.runId, "GitHub promotion run ID");
33809
+ const status = await inspectAbsoluteMobileCiRun({
33810
+ projectRoot: options.projectRoot,
33811
+ ...options.repository ? { repository: options.repository } : {},
33812
+ run: runner,
33813
+ runId: promotionRunId
33814
+ });
33815
+ if (status.status !== "completed" || status.conclusion !== "success")
33816
+ throw new TypeError("Mobile promotion run has not completed successfully.");
33817
+ const platform3 = inferPromotionPlatform(status);
33818
+ const output = projectPath(options.projectRoot, options.outputDirectory ?? `.absolutejs/mobile-ci/audits/${promotionRunId}`);
33819
+ if (await access18(output).then(() => true).catch(() => false))
33820
+ throw new TypeError(`Mobile CI audit output already exists: ${output}`);
33821
+ await mkdir18(dirname16(output), { recursive: true });
33822
+ const staging = await mkdtemp11(join24(dirname16(output), ".promotion-audit-"));
33823
+ try {
33824
+ const evidenceRoot = join24(staging, "promotion-evidence");
33825
+ await mkdir18(evidenceRoot);
33826
+ await requireCommand(runner, [
33827
+ "gh",
33828
+ "run",
33829
+ "download",
33830
+ promotionRunId,
33831
+ ...repositoryArgs(options.repository),
33832
+ "--name",
33833
+ `absolute-mobile-${platform3}-promotion-${promotionRunId}`,
33834
+ "--dir",
33835
+ evidenceRoot
33836
+ ], { cwd: options.projectRoot }, "GitHub promotion artifact download");
33837
+ const contextPaths = await findFiles(evidenceRoot, "promotion-context.json");
33838
+ let context;
33839
+ if (contextPaths.length === 1)
33840
+ context = parseAbsoluteMobilePromotionContext(await readBoundedJson(onlyPath(contextPaths, "Promotion context"), "Promotion context"));
33841
+ else if (contextPaths.length > 1)
33842
+ throw new TypeError("Promotion artifact contains duplicate contexts.");
33843
+ if (context && context.promotionRunId !== promotionRunId)
33844
+ throw new TypeError("Promotion context run identity does not match.");
33845
+ if (context && context.platform !== platform3)
33846
+ throw new TypeError("Promotion context platform does not match.");
33847
+ const sourceRunId = options.sourceRunId ? runId(options.sourceRunId, "GitHub source run ID") : context?.sourceRunId;
33848
+ if (!sourceRunId)
33849
+ throw new TypeError("Promotion artifact predates source-run context; pass --source-run-id.");
33850
+ if (context && options.sourceRunId && context.sourceRunId !== sourceRunId)
33851
+ throw new TypeError("Requested source run does not match promotion context.");
33852
+ const sourceArtifact = context?.sourceArtifact ?? `absolute-mobile-${platform3}`;
33853
+ const sourceRoot = join24(staging, "source-release");
33854
+ await mkdir18(sourceRoot);
33855
+ await requireCommand(runner, [
33856
+ "gh",
33857
+ "run",
33858
+ "download",
33859
+ sourceRunId,
33860
+ ...repositoryArgs(options.repository),
33861
+ "--name",
33862
+ sourceArtifact,
33863
+ "--dir",
33864
+ sourceRoot
33865
+ ], { cwd: options.projectRoot }, "GitHub source release download");
33866
+ const releaseMetadata = await findFiles(sourceRoot, "release.json");
33867
+ if (releaseMetadata.length !== 1)
33868
+ throw new TypeError("Source artifact must contain exactly one immutable mobile release.");
33869
+ const releaseMetadataPath = onlyPath(releaseMetadata, "Source artifact");
33870
+ const release = platform3 === "android" ? await readAbsoluteAndroidRelease(options.projectRoot, releaseMetadataPath) : await readAbsoluteIosRelease(options.projectRoot, releaseMetadataPath);
33871
+ const certificationPaths = await findFiles(evidenceRoot, "certification.json");
33872
+ const verificationPaths = await findFiles(evidenceRoot, "verification.json");
33873
+ const receiptPaths = await findFiles(evidenceRoot, "promotion-receipt.json");
33874
+ if (certificationPaths.length !== 1 || verificationPaths.length !== 1 || receiptPaths.length !== 1)
33875
+ throw new TypeError("Promotion audit artifact is incomplete or ambiguous.");
33876
+ const certificationPath = onlyPath(certificationPaths, "Promotion certification");
33877
+ const verificationPath = onlyPath(verificationPaths, "Promotion verification");
33878
+ const receiptPath = onlyPath(receiptPaths, "Promotion receipt");
33879
+ const loadedCertification = await readAbsoluteMobileReleaseCertification(options.projectRoot, certificationPath);
33880
+ const certification = verifyAbsoluteMobileReleaseCertification(loadedCertification.certification, release);
33881
+ const verification = await readAbsoluteMobileCertificationVerification(options.projectRoot, verificationPath);
33882
+ if (verification.identity.sha !== status.headSha)
33883
+ throw new TypeError("Sigstore workflow commit does not match the promotion run.");
33884
+ if (options.repository && verification.identity.repository !== options.repository)
33885
+ throw new TypeError("Sigstore repository does not match the requested repository.");
33886
+ const bundlePath = `${certificationPath}.sigstore.json`;
33887
+ const bundle = await readAbsoluteSigstoreBundle(options.projectRoot, bundlePath);
33888
+ if (canonicalJson2(bundle) !== canonicalJson2(verification.bundle))
33889
+ throw new TypeError("Sigstore bundle does not match verification envelope.");
33890
+ await requireCommand(runner, [
33891
+ "bunx",
33892
+ "@absolutejs/attest@0.2.0",
33893
+ "verify-blobs",
33894
+ certificationPath
33895
+ ], {
33896
+ cwd: options.projectRoot,
33897
+ env: {
33898
+ ...process.env,
33899
+ GITHUB_REF: verification.identity.ref,
33900
+ GITHUB_REPOSITORY: verification.identity.repository,
33901
+ GITHUB_SHA: verification.identity.sha,
33902
+ GITHUB_WORKFLOW_REF: `${verification.identity.repository}/${verification.identity.workflowPath}@${verification.identity.ref}`
33903
+ }
33904
+ }, "Sigstore certification verification");
33905
+ const publication = publicationSummary(await readBoundedJson(receiptPath, "Mobile promotion receipt"), platform3, release, certification);
33906
+ const audit = {
33907
+ certification: {
33908
+ certificationId: certification.certificationId,
33909
+ requirement: certification.requirement,
33910
+ strength: certification.strength
33911
+ },
33912
+ format: ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
33913
+ github: {
33914
+ conclusion: status.conclusion,
33915
+ promotionRunId,
33916
+ repository: verification.identity.repository,
33917
+ sourceRunId,
33918
+ url: status.url,
33919
+ workflowName: status.workflowName
33920
+ },
33921
+ platform: platform3,
33922
+ provenance: verification.identity,
33923
+ publication,
33924
+ release: {
33925
+ appId: release.metadata.appId,
33926
+ bytes: release.metadata.bytes,
33927
+ engine: release.metadata.engine,
33928
+ releaseId: release.metadata.releaseId,
33929
+ runtime: release.metadata.runtime,
33930
+ sha256: release.metadata.sha256
33931
+ },
33932
+ status: "verified",
33933
+ verifiedAt: new Date().toISOString()
33934
+ };
33935
+ await Promise.all([
33936
+ writeFile18(join24(staging, "audit.json"), `${JSON.stringify(audit, null, 2)}
33937
+ `, { mode: 384 }),
33938
+ writeFile18(join24(staging, "audit.md"), renderAuditMarkdown(audit), {
33939
+ mode: 384
33940
+ })
33941
+ ]);
33942
+ await rename14(staging, output);
33943
+ return { audit, directory: output };
33944
+ } catch (error) {
33945
+ await rm14(staging, { force: true, recursive: true });
33946
+ throw error;
33947
+ }
33948
+ };
33949
+ // src/mobile/ciWorkflow.ts
33950
+ import { existsSync as existsSync4 } from "fs";
33951
+ import { access as access19, mkdir as mkdir19, readFile as readFile23, writeFile as writeFile19 } from "fs/promises";
33952
+ import { dirname as dirname17, extname as extname5, relative as relative18, resolve as resolve20, sep as sep13 } from "path";
33953
+ var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 3;
33954
+ var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
33955
+ var CI_ENV_INDENTATION = 6;
33956
+ var RESERVED_SECRET_NAMES = new Set([
33957
+ "ABSOLUTE_ANDROID_KEYSTORE_BASE64",
33958
+ "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD",
33959
+ "ABSOLUTE_ANDROID_KEY_ALIAS",
33960
+ "ABSOLUTE_ANDROID_KEY_PASSWORD",
33961
+ "ABSOLUTE_GOOGLE_CREDENTIALS_BASE64",
33962
+ "ABSOLUTE_IOS_CERTIFICATE_BASE64",
33963
+ "ABSOLUTE_IOS_CERTIFICATE_PASSWORD",
33964
+ "ABSOLUTE_IOS_PROVISIONING_PROFILE_BASE64",
33965
+ "ABSOLUTE_IOS_KEYCHAIN_PASSWORD",
33966
+ "ABSOLUTE_IOS_DEVELOPMENT_TEAM",
33967
+ "APP_STORE_CONNECT_ISSUER_ID",
33968
+ "APP_STORE_CONNECT_KEY_ID",
33969
+ "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
33970
+ ]);
33971
+ var exists9 = async (path) => {
33972
+ try {
33973
+ await access19(path);
33974
+ return true;
33975
+ } catch {
33976
+ return false;
33977
+ }
33978
+ };
33979
+ var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
33980
+ var projectPath2 = (projectRoot, value, field2, options = {}) => {
33981
+ const root = resolve20(projectRoot);
33982
+ const path = resolve20(root, value);
33983
+ const portable = relative18(root, path).replaceAll("\\", "/");
33984
+ if (portable === ".." || portable.startsWith(`..${sep13}`) || portable.startsWith("../") || portable === "") {
33985
+ throw new TypeError(`${field2} must remain inside the project root.`);
33986
+ }
33987
+ if (/\r|\n/u.test(portable) || portable.startsWith("-"))
33988
+ throw new TypeError(`${field2} contains an unsafe path.`);
33989
+ if (!options.allowMissing && !existsSync4(path))
33990
+ throw new TypeError(`${field2} does not exist inside the project.`);
33991
+ return portable;
33992
+ };
33993
+ var workflowOutputPath = (projectRoot, value) => {
33994
+ const root = resolve20(projectRoot);
33995
+ const workflows = resolve20(root, ".github/workflows");
33996
+ const path = resolve20(root, value ?? ".github/workflows/absolute-mobile.yml");
33997
+ const portable = relative18(workflows, path);
33998
+ if (portable === ".." || portable.startsWith(`..${sep13}`) || extname5(path) !== ".yml" && extname5(path) !== ".yaml") {
33999
+ throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
34000
+ }
34001
+ return path;
34002
+ };
34003
+ var normalizeSecretEnvironment = (values = []) => {
34004
+ const names = [...new Set(values)].sort();
34005
+ for (const name of names) {
34006
+ if (!SECRET_NAME_PATTERN.test(name))
34007
+ throw new TypeError("mobile ci github --secret-env values must be uppercase environment variable names.");
34008
+ if (name.startsWith("GITHUB_") || name.startsWith("RUNNER_") || name.startsWith("ACTIONS_") || RESERVED_SECRET_NAMES.has(name)) {
34009
+ throw new TypeError(`mobile ci github --secret-env cannot replace reserved variable ${name}.`);
34010
+ }
34011
+ }
34012
+ return names;
34013
+ };
34014
+ var customSecretEnvironment = (names, indentation = CI_ENV_INDENTATION) => names.map((name) => `${" ".repeat(indentation)}${name}: \${{ secrets.${name} }}`).join(`
34015
+ `);
34016
+ var commandEnvironment2 = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
34017
+ ABSOLUTE_REGISTRY_MODULE: ${yamlString(options.registryModule)}
34018
+ ABSOLUTE_SERVER_ENTRY: ${yamlString(options.serverEntry)}`;
34019
+ var appendConfigArgument = `if [[ -n "$ABSOLUTE_CONFIG_PATH" ]]; then
34020
+ args+=(--config "$ABSOLUTE_CONFIG_PATH")
34021
+ fi`;
34022
+ var installSteps = ` - name: Check out source
34023
+ uses: actions/checkout@v6
34024
+ - name: Install Bun
34025
+ uses: oven-sh/setup-bun@v2
34026
+ - name: Install exact dependencies
34027
+ run: bun ci`;
34028
+ var bundleAuditSteps = ` - name: Prepare production mobile bundle
34029
+ shell: bash
34030
+ run: |
34031
+ args=(bunx absolute prepare "$ABSOLUTE_SERVER_ENTRY" --outdir .absolutejs/mobile-ci/server)
34032
+ ${appendConfigArgument}
34033
+ "\${args[@]}"
34034
+ - name: Run cryptographic mobile bundle audit
34035
+ id: mobile-bundle-audit
34036
+ continue-on-error: true
34037
+ shell: bash
34038
+ run: |
34039
+ mkdir -p .absolutejs/mobile-ci
34040
+ args=(bunx absolute mobile inspect --json --require-bundle)
34041
+ ${appendConfigArgument}
34042
+ "\${args[@]}" > .absolutejs/mobile-ci/inspection.json
34043
+ - name: Upload mobile inspection report
34044
+ if: always()
34045
+ uses: actions/upload-artifact@v7
34046
+ with:
34047
+ name: absolute-mobile-inspection
34048
+ path: .absolutejs/mobile-ci/inspection.json
34049
+ if-no-files-found: error
34050
+ retention-days: 30
34051
+ include-hidden-files: true
34052
+ - name: Enforce mobile bundle audit
34053
+ if: steps.mobile-bundle-audit.outcome != 'success'
34054
+ run: exit 1`;
34055
+ var releaseAuditSteps = (platform3) => ` - name: Run redacted mobile release audit
34056
+ id: mobile-release-audit
34057
+ continue-on-error: true
34058
+ shell: bash
34059
+ run: |
34060
+ mkdir -p .absolutejs/mobile-ci
34061
+ args=(bunx absolute mobile doctor release ${platform3} --json)
34062
+ ${appendConfigArgument}
34063
+ "\${args[@]}" > .absolutejs/mobile-ci/compliance.json
34064
+ - name: Upload mobile compliance report
34065
+ if: always()
34066
+ uses: actions/upload-artifact@v7
34067
+ with:
34068
+ name: absolute-mobile-compliance-\${{ github.job }}
34069
+ path: .absolutejs/mobile-ci/compliance.json
34070
+ if-no-files-found: error
34071
+ retention-days: 30
34072
+ include-hidden-files: true
34073
+ - name: Enforce mobile release audit
34074
+ if: steps.mobile-release-audit.outcome != 'success'
34075
+ run: exit 1`;
34076
+ var platformInput = (platforms) => {
34077
+ const choices = platforms.length === 2 ? ["all", ...platforms] : platforms;
34078
+ return ` platform:
34079
+ description: Native platform to build
34080
+ required: true
34081
+ type: choice
34082
+ default: ${choices[0]}
33166
34083
  options:
33167
34084
  ${choices.map((value) => ` - ${value}`).join(`
33168
34085
  `)}`;
@@ -33575,6 +34492,10 @@ var promotionSharedSteps = (platform3) => ` - name: Restore exact ${platfor
33575
34492
  path: .absolutejs/mobile/releases/${platform3}
33576
34493
  run-id: \${{ inputs.source_run_id }}
33577
34494
  github-token: \${{ github.token }}
34495
+ - name: Record bounded promotion context
34496
+ shell: bash
34497
+ run: |
34498
+ bun -e 'const sourceRunId = process.env.ABSOLUTE_SOURCE_RUN_ID; const promotionRunId = process.env.GITHUB_RUN_ID; if (!sourceRunId?.match(/^[1-9][0-9]*$/) || !promotionRunId?.match(/^[1-9][0-9]*$/)) throw new Error("Invalid promotion run identity"); await Bun.write(".absolutejs/mobile-ci/promotion-context.json", JSON.stringify({ format: 1, platform: ${JSON.stringify(platform3)}, promotionRunId, sourceArtifact: ${JSON.stringify(`absolute-mobile-${platform3}`)}, sourceRunId }, null, 2) + "\\n");'
33578
34499
  - name: Import and verify exact certification
33579
34500
  shell: bash
33580
34501
  run: |
@@ -33613,6 +34534,7 @@ var androidPromotionJob = (options) => {
33613
34534
  env:
33614
34535
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
33615
34536
  ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
34537
+ ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
33616
34538
  ABSOLUTE_PLAY_TRACK: \${{ inputs.play_track }}
33617
34539
  ABSOLUTE_GOOGLE_CREDENTIALS_BASE64: \${{ secrets.ABSOLUTE_GOOGLE_CREDENTIALS_BASE64 }}
33618
34540
  GOOGLE_APPLICATION_CREDENTIALS: \${{ runner.temp }}/absolute-google-credentials.json${custom ? `
@@ -33652,6 +34574,7 @@ ${promotionSharedSteps("android")}
33652
34574
  name: absolute-mobile-android-promotion-\${{ github.run_id }}
33653
34575
  path: |
33654
34576
  .absolutejs/mobile-ci/imported-certification/
34577
+ .absolutejs/mobile-ci/promotion-context.json
33655
34578
  .absolutejs/mobile-ci/promotion-receipt.json
33656
34579
  if-no-files-found: error
33657
34580
  retention-days: 90
@@ -33676,6 +34599,7 @@ var iosPromotionJob = (options) => {
33676
34599
  env:
33677
34600
  ABSOLUTE_CERTIFICATION_BASE64: \${{ inputs.certification_base64 }}
33678
34601
  ABSOLUTE_RELEASE_CHANNEL: \${{ inputs.channel }}
34602
+ ABSOLUTE_SOURCE_RUN_ID: \${{ inputs.source_run_id }}
33679
34603
  ABSOLUTE_TESTFLIGHT_GROUP: \${{ inputs.testflight_group }}
33680
34604
  ABSOLUTE_TESTFLIGHT_SUBMIT_REVIEW: \${{ inputs.submit_testflight_review }}
33681
34605
  APP_STORE_CONNECT_ISSUER_ID: \${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
@@ -33717,6 +34641,7 @@ ${promotionSharedSteps("ios")}
33717
34641
  name: absolute-mobile-ios-promotion-\${{ github.run_id }}
33718
34642
  path: |
33719
34643
  .absolutejs/mobile-ci/imported-certification/
34644
+ .absolutejs/mobile-ci/promotion-context.json
33720
34645
  .absolutejs/mobile-ci/promotion-receipt.json
33721
34646
  if-no-files-found: error
33722
34647
  retention-days: 90
@@ -33753,9 +34678,9 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
33753
34678
  ].sort();
33754
34679
  const includePublishing = options.includePublishing === true;
33755
34680
  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 });
34681
+ const serverEntry = projectPath2(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
34682
+ const configPath = options.configPath ? projectPath2(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
34683
+ const registryModule = projectPath2(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
33759
34684
  const environment = commandEnvironment2({
33760
34685
  configPath,
33761
34686
  registryModule,
@@ -33819,12 +34744,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
33819
34744
  var writeAbsoluteMobileGithubWorkflow = async (options) => {
33820
34745
  const path = workflowOutputPath(options.projectRoot, options.outputPath);
33821
34746
  const generated = createAbsoluteMobileGithubWorkflow(options);
33822
- const previous = await exists8(path) ? await readFile20(path, "utf8") : undefined;
34747
+ const previous = await exists9(path) ? await readFile23(path, "utf8") : undefined;
33823
34748
  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.`);
34749
+ throw new TypeError(`${relative18(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
33825
34750
  if (previous !== generated.workflow) {
33826
- await mkdir16(dirname15(path), { recursive: true });
33827
- await writeFile16(path, generated.workflow);
34751
+ await mkdir19(dirname17(path), { recursive: true });
34752
+ await writeFile19(path, generated.workflow);
33828
34753
  }
33829
34754
  return {
33830
34755
  changed: previous !== generated.workflow,
@@ -33842,20 +34767,20 @@ init_materializedBundle();
33842
34767
  init_mobilePreview();
33843
34768
 
33844
34769
  // 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";
34770
+ import { readFile as readFile24, rename as rename15, writeFile as writeFile20 } from "fs/promises";
34771
+ import { join as join27 } from "path";
33847
34772
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
33848
34773
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
33849
34774
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
33850
34775
  var NOT_FOUND = -1;
33851
34776
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
33852
34777
  var writeChangedFile = async (path, source) => {
33853
- const current = await readFile21(path, "utf8");
34778
+ const current = await readFile24(path, "utf8");
33854
34779
  if (current === source)
33855
34780
  return false;
33856
34781
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
33857
- await writeFile17(temporary, source, { flag: "wx" });
33858
- await rename13(temporary, path);
34782
+ await writeFile20(temporary, source, { flag: "wx" });
34783
+ await rename15(temporary, path);
33859
34784
  return true;
33860
34785
  };
33861
34786
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -33900,8 +34825,8 @@ ${hosts}
33900
34825
  `;
33901
34826
  };
33902
34827
  var configureAndroid = async (config) => {
33903
- const path = join25(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
33904
- const source = await readFile21(path, "utf8");
34828
+ const path = join27(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
34829
+ const source = await readFile24(path, "utf8");
33905
34830
  const mainActivity = source.indexOf('android:name=".MainActivity"');
33906
34831
  if (mainActivity === NOT_FOUND) {
33907
34832
  throw new TypeError("Android MainActivity was not found.");
@@ -33926,8 +34851,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
33926
34851
  ${END_MARKER}
33927
34852
  `;
33928
34853
  var configureIosInfo = async (config) => {
33929
- const path = join25(config.nativeProjectDirectory, "ios/App/App/Info.plist");
33930
- const source = await readFile21(path, "utf8");
34854
+ const path = join27(config.nativeProjectDirectory, "ios/App/App/Info.plist");
34855
+ const source = await readFile24(path, "utf8");
33931
34856
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
33932
34857
  ${END_MARKER}
33933
34858
  `;
@@ -33950,10 +34875,10 @@ ${domains}
33950
34875
  `;
33951
34876
  };
33952
34877
  var configureIosEntitlements = async (config) => {
33953
- const path = join25(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
34878
+ const path = join27(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
33954
34879
  let current = "";
33955
34880
  try {
33956
- current = await readFile21(path, "utf8");
34881
+ current = await readFile24(path, "utf8");
33957
34882
  } catch (error) {
33958
34883
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
33959
34884
  throw error;
@@ -33963,13 +34888,13 @@ var configureIosEntitlements = async (config) => {
33963
34888
  if (current === source)
33964
34889
  return false;
33965
34890
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
33966
- await writeFile17(temporary, source, { flag: "wx" });
33967
- await rename13(temporary, path);
34891
+ await writeFile20(temporary, source, { flag: "wx" });
34892
+ await rename15(temporary, path);
33968
34893
  return true;
33969
34894
  };
33970
34895
  var configureIosProject = async (config) => {
33971
- const path = join25(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
33972
- const source = await readFile21(path, "utf8");
34896
+ const path = join27(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
34897
+ const source = await readFile24(path, "utf8");
33973
34898
  const declarations = [
33974
34899
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
33975
34900
  ].map((match) => match[1]);
@@ -34008,8 +34933,8 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
34008
34933
  };
34009
34934
  // src/mobile/nativeDeviceCapabilities.ts
34010
34935
  init_deviceCapabilities();
34011
- import { readFile as readFile22, rename as rename14, writeFile as writeFile18 } from "fs/promises";
34012
- import { join as join26 } from "path";
34936
+ import { readFile as readFile25, rename as rename16, writeFile as writeFile21 } from "fs/promises";
34937
+ import { join as join28 } from "path";
34013
34938
  var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
34014
34939
  var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
34015
34940
  var NOT_FOUND2 = -1;
@@ -34019,12 +34944,12 @@ var PUSH_START_MARKER = "absolutejs:push-notifications:start";
34019
34944
  var PUSH_END_MARKER = "absolutejs:push-notifications:end";
34020
34945
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
34021
34946
  var writeChangedFile2 = async (path, source) => {
34022
- const current = await readFile22(path, "utf8");
34947
+ const current = await readFile25(path, "utf8");
34023
34948
  if (current === source)
34024
34949
  return false;
34025
34950
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
34026
- await writeFile18(temporary, source, { flag: "wx" });
34027
- await rename14(temporary, path);
34951
+ await writeFile21(temporary, source, { flag: "wx" });
34952
+ await rename16(temporary, path);
34028
34953
  return true;
34029
34954
  };
34030
34955
  var writeOptionalChangedFile = async (path, source) => {
@@ -34032,14 +34957,14 @@ var writeOptionalChangedFile = async (path, source) => {
34032
34957
  if (current === source)
34033
34958
  return false;
34034
34959
  if (current === null) {
34035
- await writeFile18(path, source, { flag: "wx" });
34960
+ await writeFile21(path, source, { flag: "wx" });
34036
34961
  return true;
34037
34962
  }
34038
34963
  return writeChangedFile2(path, source);
34039
34964
  };
34040
34965
  var optionalSource = async (path) => {
34041
34966
  try {
34042
- return await readFile22(path, "utf8");
34967
+ return await readFile25(path, "utf8");
34043
34968
  } catch (error) {
34044
34969
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
34045
34970
  return null;
@@ -34153,15 +35078,15 @@ var writeIosPrivacyManifest = async (path, current, source) => {
34153
35078
  return false;
34154
35079
  if (current !== null)
34155
35080
  return writeChangedFile2(path, source);
34156
- await writeFile18(path, source, { flag: "wx" });
35081
+ await writeFile21(path, source, { flag: "wx" });
34157
35082
  return true;
34158
35083
  };
34159
35084
  var configureIosPrivacyProject = async (config, requirements) => {
34160
35085
  if (requirements.iosPrivacyAccessedApis.length === 0)
34161
35086
  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));
35087
+ const projectPath3 = join28(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
35088
+ const project = await readFile25(projectPath3, "utf8");
35089
+ return writeChangedFile2(projectPath3, addIosPrivacyProjectReference(project));
34165
35090
  };
34166
35091
  var addIosPrivacyProjectReference = (source) => {
34167
35092
  const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
@@ -34212,8 +35137,8 @@ ${next.slice(index)}`;
34212
35137
  return next;
34213
35138
  };
34214
35139
  var configureIos2 = async (config, plan) => {
34215
- const path = join26(config.nativeProjectDirectory, "ios/App/App/Info.plist");
34216
- const source = await readFile22(path, "utf8");
35140
+ const path = join28(config.nativeProjectDirectory, "ios/App/App/Info.plist");
35141
+ const source = await readFile25(path, "utf8");
34217
35142
  const requirements = absoluteDeviceNativeRequirements(plan);
34218
35143
  const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
34219
35144
  const ownedStart = source.indexOf(START_MARKER2);
@@ -34233,7 +35158,7 @@ ${content}
34233
35158
  ${END_MARKER2}
34234
35159
  ` : "";
34235
35160
  const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
34236
- const privacyPath = join26(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
35161
+ const privacyPath = join28(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
34237
35162
  const privacyCurrent = await optionalSource(privacyPath);
34238
35163
  const privacySource = privacyManifestSource(privacyCurrent, requirements);
34239
35164
  const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
@@ -34244,7 +35169,7 @@ ${content}
34244
35169
  return infoChanged || privacyChanged || projectChanged || pushChanged;
34245
35170
  };
34246
35171
  var configureIosPushNotifications = async (config, enabled) => {
34247
- const entitlementsPath = join26(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
35172
+ const entitlementsPath = join28(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
34248
35173
  const entitlements = await optionalSource(entitlementsPath);
34249
35174
  if (entitlements === null && !enabled)
34250
35175
  return false;
@@ -34256,7 +35181,7 @@ var configureIosPushNotifications = async (config, enabled) => {
34256
35181
  <!-- ${PUSH_END_MARKER} -->
34257
35182
  ` : "";
34258
35183
  const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
34259
- const delegatePath = join26(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
35184
+ const delegatePath = join28(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
34260
35185
  const delegate = await optionalSource(delegatePath);
34261
35186
  if (delegate === null && !enabled)
34262
35187
  return false;
@@ -34305,8 +35230,8 @@ var replacePushRegion = (source, region, insertion) => {
34305
35230
  return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
34306
35231
  };
34307
35232
  var configureAndroid2 = async (config, plan) => {
34308
- const path = join26(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
34309
- const source = await readFile22(path, "utf8");
35233
+ const path = join28(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
35234
+ const source = await readFile25(path, "utf8");
34310
35235
  const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
34311
35236
  const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
34312
35237
  `);
@@ -34341,7 +35266,7 @@ ${content}
34341
35266
  throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
34342
35267
  const [manifestChanged, firebaseChanged] = await Promise.all([
34343
35268
  writeChangedFile2(path, nextManifest),
34344
- writeOptionalChangedFile(join26(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
35269
+ writeOptionalChangedFile(join28(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
34345
35270
  ]);
34346
35271
  return manifestChanged || firebaseChanged;
34347
35272
  };
@@ -34360,14 +35285,14 @@ init_nativeAuth();
34360
35285
 
34361
35286
  // src/mobile/nativeUpdates.ts
34362
35287
  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
35288
+ mkdir as mkdir20,
35289
+ readFile as readFile26,
35290
+ readdir as readdir8,
35291
+ rename as rename17,
35292
+ rm as rm15,
35293
+ writeFile as writeFile22
34369
35294
  } from "fs/promises";
34370
- import { dirname as dirname17, join as join27 } from "path";
35295
+ import { dirname as dirname19, join as join29 } from "path";
34371
35296
  var START = "// absolutejs:mobile-updates:start";
34372
35297
  var END = "// absolutejs:mobile-updates:end";
34373
35298
  var PLUGIN_START = "// absolutejs:mobile-update-plugin:start";
@@ -34375,32 +35300,32 @@ var PLUGIN_END = "// absolutejs:mobile-update-plugin:end";
34375
35300
  var ANDROID_PLUGIN = "AbsoluteMobileUpdateWatchdogPlugin.java";
34376
35301
  var optionalSource2 = async (path) => {
34377
35302
  try {
34378
- return await readFile23(path, "utf8");
35303
+ return await readFile26(path, "utf8");
34379
35304
  } catch (error) {
34380
35305
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
34381
35306
  return null;
34382
35307
  throw error;
34383
35308
  }
34384
35309
  };
34385
- var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35310
+ var isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34386
35311
  var writeChanged = async (path, source) => {
34387
35312
  const current = await optionalSource2(path);
34388
35313
  if (current === source)
34389
35314
  return false;
34390
- await mkdir17(dirname17(path), { recursive: true });
35315
+ await mkdir20(dirname19(path), { recursive: true });
34391
35316
  if (current === null) {
34392
- await writeFile19(path, source, { flag: "wx" });
35317
+ await writeFile22(path, source, { flag: "wx" });
34393
35318
  return true;
34394
35319
  }
34395
35320
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
34396
- await writeFile19(temporary, source, { flag: "wx" });
34397
- await rename15(temporary, path);
35321
+ await writeFile22(temporary, source, { flag: "wx" });
35322
+ await rename17(temporary, path);
34398
35323
  return true;
34399
35324
  };
34400
35325
  var removeOwnedFile = async (path) => {
34401
35326
  if (await optionalSource2(path) === null)
34402
35327
  return false;
34403
- await rm13(path);
35328
+ await rm15(path);
34404
35329
  return true;
34405
35330
  };
34406
35331
  var replaceMarkedRegion = (source, start, end, region, insertion, error) => {
@@ -34537,9 +35462,9 @@ public final class AbsoluteMobileUpdateWatchdogPlugin: CAPPlugin, CAPBridgedPlug
34537
35462
  ${PLUGIN_END}
34538
35463
  `;
34539
35464
  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");
35465
+ const path = join29(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
35466
+ const capacitorConfigPath = join29(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
35467
+ let source = await readFile26(path, "utf8");
34543
35468
  const launch2 = source.indexOf("didFinishLaunchingWithOptions");
34544
35469
  const brace = launch2 < 0 ? -1 : source.indexOf("{", launch2);
34545
35470
  const launchRegion = enabled ? ` ${START}
@@ -34550,11 +35475,11 @@ var configureIos3 = async (config, enabled) => {
34550
35475
  `, brace) + 1, "Could not find a safe iOS location for mobile update recovery.");
34551
35476
  const plugin = enabled ? iosPluginRegion(config.updates?.bootTimeoutMs ?? 20000) : "";
34552
35477
  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");
35478
+ const capacitorConfigSource2 = await readFile26(capacitorConfigPath, "utf8");
34554
35479
  let capacitorConfig;
34555
35480
  try {
34556
35481
  const value = JSON.parse(capacitorConfigSource2);
34557
- if (!isRecord12(value))
35482
+ if (!isRecord15(value))
34558
35483
  throw new TypeError;
34559
35484
  capacitorConfig = value;
34560
35485
  } catch {
@@ -34723,19 +35648,19 @@ public final class AbsoluteMobileUpdateWatchdogPlugin extends Plugin {
34723
35648
  var androidActivityInRoot = async (root) => {
34724
35649
  let entries;
34725
35650
  try {
34726
- entries = await readdir7(root, { recursive: true });
35651
+ entries = await readdir8(root, { recursive: true });
34727
35652
  } catch (error) {
34728
35653
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
34729
35654
  return;
34730
35655
  throw error;
34731
35656
  }
34732
35657
  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;
35658
+ return candidate ? join29(root, candidate) : undefined;
34734
35659
  };
34735
35660
  var androidActivity = async (config) => {
34736
35661
  const roots = [
34737
- join27(config.nativeProjectDirectory, "android/app/src/main/java"),
34738
- join27(config.nativeProjectDirectory, "android/app/src/main/kotlin")
35662
+ join29(config.nativeProjectDirectory, "android/app/src/main/java"),
35663
+ join29(config.nativeProjectDirectory, "android/app/src/main/kotlin")
34739
35664
  ];
34740
35665
  const candidates = await Promise.all(roots.map(androidActivityInRoot));
34741
35666
  const candidate = candidates.find((value) => value !== undefined);
@@ -34834,12 +35759,12 @@ var injectAndroidActivity = (source, enabled) => {
34834
35759
  };
34835
35760
  var configureAndroid3 = async (config, enabled) => {
34836
35761
  const activityPath = await androidActivity(config);
34837
- const activity = await readFile23(activityPath, "utf8");
35762
+ const activity = await readFile26(activityPath, "utf8");
34838
35763
  const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
34839
35764
  if (!packageName)
34840
35765
  throw new TypeError("Android MainActivity package declaration was not found.");
34841
35766
  const packagePath = packageName.replaceAll(".", "/");
34842
- const pluginPath = join27(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
35767
+ const pluginPath = join29(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
34843
35768
  const changed = await Promise.all([
34844
35769
  writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
34845
35770
  enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
@@ -34859,14 +35784,14 @@ var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) =>
34859
35784
  };
34860
35785
  // src/mobile/nativeObservability.ts
34861
35786
  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
35787
+ mkdir as mkdir21,
35788
+ readFile as readFile27,
35789
+ readdir as readdir9,
35790
+ rename as rename18,
35791
+ rm as rm16,
35792
+ writeFile as writeFile23
34868
35793
  } from "fs/promises";
34869
- import { dirname as dirname18, join as join28 } from "path";
35794
+ import { dirname as dirname20, join as join30 } from "path";
34870
35795
  var START2 = "// absolutejs:native-observability:start";
34871
35796
  var END2 = "// absolutejs:native-observability:end";
34872
35797
  var PLUGIN_START2 = "// absolutejs:native-observability-plugin:start";
@@ -34874,7 +35799,7 @@ var PLUGIN_END2 = "// absolutejs:native-observability-plugin:end";
34874
35799
  var ANDROID_PLUGIN2 = "AbsoluteMobileObservabilityPlugin.java";
34875
35800
  var optionalSource3 = async (path) => {
34876
35801
  try {
34877
- return await readFile24(path, "utf8");
35802
+ return await readFile27(path, "utf8");
34878
35803
  } catch (error) {
34879
35804
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
34880
35805
  return null;
@@ -34885,23 +35810,23 @@ var writeChanged2 = async (path, source) => {
34885
35810
  const current = await optionalSource3(path);
34886
35811
  if (current === source)
34887
35812
  return false;
34888
- await mkdir18(dirname18(path), { recursive: true });
35813
+ await mkdir21(dirname20(path), { recursive: true });
34889
35814
  if (current === null) {
34890
- await writeFile20(path, source, { flag: "wx" });
35815
+ await writeFile23(path, source, { flag: "wx" });
34891
35816
  return true;
34892
35817
  }
34893
35818
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
34894
- await writeFile20(temporary, source, { flag: "wx" });
34895
- await rename16(temporary, path);
35819
+ await writeFile23(temporary, source, { flag: "wx" });
35820
+ await rename18(temporary, path);
34896
35821
  return true;
34897
35822
  };
34898
35823
  var removeOwnedFile2 = async (path) => {
34899
35824
  if (await optionalSource3(path) === null)
34900
35825
  return false;
34901
- await rm14(path);
35826
+ await rm16(path);
34902
35827
  return true;
34903
35828
  };
34904
- var isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35829
+ var isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34905
35830
  var replaceMarkedRegion2 = (source, start, end, region, insertion, error) => {
34906
35831
  const existingStart = source.indexOf(start);
34907
35832
  const existingEnd = source.indexOf(end);
@@ -35034,9 +35959,9 @@ public final class AbsoluteMobileObservabilityPlugin: CAPPlugin, CAPBridgedPlugi
35034
35959
  ${PLUGIN_END2}
35035
35960
  `;
35036
35961
  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");
35962
+ const path = join30(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
35963
+ const capacitorConfigPath = join30(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
35964
+ let source = await readFile27(path, "utf8");
35040
35965
  if (enabled && !source.includes("import MetricKit")) {
35041
35966
  const capacitorImport = source.lastIndexOf("import Capacitor");
35042
35967
  if (capacitorImport < 0)
@@ -35050,11 +35975,11 @@ ${source.slice(newline + 1)}`;
35050
35975
  source = source.replace(`import MetricKit
35051
35976
  `, "");
35052
35977
  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");
35978
+ const capacitorConfigSource2 = await readFile27(capacitorConfigPath, "utf8");
35054
35979
  let capacitorConfig;
35055
35980
  try {
35056
35981
  const value = JSON.parse(capacitorConfigSource2);
35057
- if (!isRecord13(value))
35982
+ if (!isRecord16(value))
35058
35983
  throw new TypeError;
35059
35984
  capacitorConfig = value;
35060
35985
  } catch {
@@ -35288,19 +36213,19 @@ public final class AbsoluteMobileObservabilityPlugin extends Plugin {
35288
36213
  var androidActivityInRoot2 = async (root) => {
35289
36214
  let entries;
35290
36215
  try {
35291
- entries = await readdir8(root, { recursive: true });
36216
+ entries = await readdir9(root, { recursive: true });
35292
36217
  } catch (error) {
35293
36218
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
35294
36219
  return;
35295
36220
  throw error;
35296
36221
  }
35297
36222
  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;
36223
+ return candidate ? join30(root, candidate) : undefined;
35299
36224
  };
35300
36225
  var androidActivity2 = async (config) => {
35301
36226
  const roots = [
35302
- join28(config.nativeProjectDirectory, "android/app/src/main/java"),
35303
- join28(config.nativeProjectDirectory, "android/app/src/main/kotlin")
36227
+ join30(config.nativeProjectDirectory, "android/app/src/main/java"),
36228
+ join30(config.nativeProjectDirectory, "android/app/src/main/kotlin")
35304
36229
  ];
35305
36230
  const candidates = await Promise.all(roots.map(androidActivityInRoot2));
35306
36231
  const candidate = candidates.find((value) => value !== undefined);
@@ -35349,148 +36274,14 @@ var injectAndroidActivity2 = (source, enabled) => {
35349
36274
  return `${source.slice(0, from)}${source.slice(through)}`;
35350
36275
  return `${source.slice(0, from)}${androidRegion2(kotlin, generatedMethod)}${source.slice(through)}`;
35351
36276
  }
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
- }
36277
+ if (!enabled)
36278
+ return source;
35486
36279
  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
36280
  if (onCreate >= 0) {
35488
36281
  const brace = source.indexOf("{", onCreate);
35489
36282
  const insert = source.indexOf(`
35490
36283
  `, 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)}`;
36284
+ return `${source.slice(0, insert)}${androidRegion2(kotlin, false)}${source.slice(insert)}`;
35494
36285
  }
35495
36286
  const close = source.lastIndexOf("}");
35496
36287
  if (close < 0)
@@ -35498,633 +36289,254 @@ var injectRegistration = (source) => {
35498
36289
  const separator = source[close - 1] === `
35499
36290
  ` ? "" : `
35500
36291
  `;
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);
36292
+ return `${source.slice(0, close)}${separator}${androidRegion2(kotlin, true)}${source.slice(close)}`;
35583
36293
  };
35584
- var configureAndroid5 = async (config) => {
35585
- const mainActivityPath = await activityPath(config);
35586
- const activity = await readFile25(mainActivityPath, "utf8");
36294
+ var configureAndroid4 = async (config, enabled) => {
36295
+ const activityPath = await androidActivity2(config);
36296
+ const activity = await readFile27(activityPath, "utf8");
35587
36297
  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
- };
36298
+ if (!packageName)
36299
+ throw new TypeError("Android MainActivity package declaration was not found.");
36300
+ const pluginPath = join30(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN2);
36301
+ const changed = await Promise.all([
36302
+ writeChanged2(activityPath, injectAndroidActivity2(activity, enabled)),
36303
+ enabled ? writeChanged2(pluginPath, androidPluginSource2(packageName)) : removeOwnedFile2(pluginPath)
36304
+ ]);
36305
+ return changed.some(Boolean);
35765
36306
  };
35766
- var validateEvidence = async (projectRoot, requested, release) => {
35767
- const path = await resolveEvidencePath(projectRoot, requested);
35768
- const bytes = await readFile26(path);
35769
- let value;
36307
+ var applyAbsoluteNativeObservability = async (config, platforms = config.platforms) => {
36308
+ if (config.engine !== "capacitor")
36309
+ return { changed: false };
36310
+ const enabled = config.observability !== undefined;
36311
+ const changed = [];
36312
+ if (platforms.includes("ios"))
36313
+ changed.push(await configureIos4(config, enabled));
36314
+ if (platforms.includes("android"))
36315
+ changed.push(await configureAndroid4(config, enabled));
36316
+ return { changed: changed.some(Boolean) };
36317
+ };
36318
+ // src/mobile/nativeReleaseReadiness.ts
36319
+ import { mkdir as mkdir22, readFile as readFile28, readdir as readdir10, rename as rename19, writeFile as writeFile24 } from "fs/promises";
36320
+ import { dirname as dirname21, join as join31 } from "path";
36321
+ var START3 = "// absolutejs:release-readiness:start";
36322
+ var END3 = "// absolutejs:release-readiness:end";
36323
+ var IOS_PLUGIN_START = "// absolutejs:release-readiness-plugin:start";
36324
+ var IOS_PLUGIN_END = "// absolutejs:release-readiness-plugin:end";
36325
+ var ANDROID_PLUGIN3 = "AbsoluteReleaseReadinessPlugin.java";
36326
+ var optionalSource4 = async (path) => {
35770
36327
  try {
35771
- value = JSON.parse(bytes.toString("utf8"));
35772
- } catch {
35773
- throw new TypeError("Mobile certification evidence report is not valid JSON.");
36328
+ return await readFile28(path, "utf8");
36329
+ } catch (error) {
36330
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
36331
+ return null;
36332
+ throw error;
35774
36333
  }
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
36334
  };
35788
- var satisfiesRequirement = (strength, requirement) => {
35789
- if (requirement === "installed")
35790
- return strength === "installed";
35791
- if (strength === "installed")
36335
+ var writeChanged3 = async (path, source) => {
36336
+ const current = await optionalSource4(path);
36337
+ if (current === source)
35792
36338
  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;
36339
+ await mkdir22(dirname21(path), { recursive: true });
36340
+ if (current === null) {
36341
+ await writeFile24(path, source, { flag: "wx" });
36342
+ return true;
35808
36343
  }
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.");
36344
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
36345
+ await writeFile24(temporary, source, { flag: "wx" });
36346
+ await rename19(temporary, path);
36347
+ return true;
35814
36348
  };
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;
36349
+ var activityInRoot = async (root) => {
36350
+ let entries;
36351
+ try {
36352
+ entries = await readdir10(root, { recursive: true });
36353
+ } catch (error) {
36354
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
36355
+ return;
36356
+ throw error;
36357
+ }
36358
+ const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
36359
+ return candidate ? join31(root, candidate) : undefined;
35824
36360
  };
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
- };
36361
+ var activityPath = async (config) => {
36362
+ const roots = [
36363
+ join31(config.nativeProjectDirectory, "android/app/src/main/java"),
36364
+ join31(config.nativeProjectDirectory, "android/app/src/main/kotlin")
36365
+ ];
36366
+ const candidates = await Promise.all(roots.map(activityInRoot));
36367
+ const candidate = candidates.find((value) => value !== undefined);
36368
+ if (candidate)
36369
+ return candidate;
36370
+ throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
35853
36371
  };
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")
36372
+ var registrationRegion = (kotlin, generatedMethod) => {
36373
+ if (generatedMethod)
36374
+ return kotlin ? ` ${START3}
36375
+ override fun onCreate(savedInstanceState: android.os.Bundle?) {
36376
+ registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
36377
+ super.onCreate(savedInstanceState)
35891
36378
  }
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 };
36379
+ ${END3}
36380
+ ` : ` ${START3}
36381
+ @Override
36382
+ public void onCreate(android.os.Bundle savedInstanceState) {
36383
+ registerPlugin(AbsoluteReleaseReadinessPlugin.class);
36384
+ super.onCreate(savedInstanceState);
36385
+ }
36386
+ ${END3}
36387
+ `;
36388
+ return kotlin ? ` ${START3}
36389
+ registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
36390
+ ${END3}
36391
+ ` : ` ${START3}
36392
+ registerPlugin(AbsoluteReleaseReadinessPlugin.class);
36393
+ ${END3}
36394
+ `;
35935
36395
  };
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}
36396
+ var injectRegistration = (source) => {
36397
+ const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
36398
+ const existingStart = source.indexOf(START3);
36399
+ const existingEnd = source.indexOf(END3);
36400
+ if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
36401
+ throw new TypeError("AbsoluteJS release-readiness markers are malformed.");
36402
+ if (existingStart >= 0) {
36403
+ const from = source.lastIndexOf(`
36404
+ `, existingStart) + 1;
36405
+ const newline = source.indexOf(`
36406
+ `, existingEnd + END3.length);
36407
+ const through = newline < 0 ? source.length : newline + 1;
36408
+ const generatedMethod = source.slice(from, through).includes("onCreate(");
36409
+ return `${source.slice(0, from)}${registrationRegion(kotlin, generatedMethod)}${source.slice(through)}`;
36410
+ }
36411
+ 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);
36412
+ if (onCreate >= 0) {
36413
+ const brace = source.indexOf("{", onCreate);
36414
+ const insert = source.indexOf(`
36415
+ `, brace) + 1;
36416
+ if (insert <= 0)
36417
+ throw new TypeError("Could not inject Android release readiness into MainActivity.");
36418
+ return `${source.slice(0, insert)}${registrationRegion(kotlin, false)}${source.slice(insert)}`;
36419
+ }
36420
+ const close = source.lastIndexOf("}");
36421
+ if (close < 0)
36422
+ throw new TypeError("Could not find the Android MainActivity class body.");
36423
+ const separator = source[close - 1] === `
36424
+ ` ? "" : `
36425
+ `;
36426
+ return `${source.slice(0, close)}${separator}${registrationRegion(kotlin, true)}${source.slice(close)}`;
36427
+ };
36428
+ var pluginSource = (packageName) => `package ${packageName};
35955
36429
 
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.
36430
+ import android.util.Log;
36431
+ import com.getcapacitor.JSObject;
36432
+ import com.getcapacitor.Plugin;
36433
+ import com.getcapacitor.PluginCall;
36434
+ import com.getcapacitor.PluginMethod;
36435
+ import com.getcapacitor.annotation.CapacitorPlugin;
35957
36436
 
35958
- | Strength | Network unavailable | Execution | Report SHA-256 |
35959
- | --- | --- | --- | --- |
35960
- ${evidence}
36437
+ @CapacitorPlugin(name = "AbsoluteReleaseReadiness")
36438
+ public final class AbsoluteReleaseReadinessPlugin extends Plugin {
36439
+ @PluginMethod
36440
+ public void ready(PluginCall call) {
36441
+ Log.i("AbsoluteJS", "Capacitor embedded web content ready");
36442
+ call.resolve(new JSObject().put("ready", true));
36443
+ }
36444
+ }
35961
36445
  `;
35962
- };
35963
- var serializeAbsoluteMobileReleaseCertification = (certification) => `${JSON.stringify(certification, null, 2)}
36446
+ var iosPluginRegion3 = `${IOS_PLUGIN_START}
36447
+ @objc(AbsoluteReleaseReadinessPlugin)
36448
+ public final class AbsoluteReleaseReadinessPlugin: CAPPlugin, CAPBridgedPlugin {
36449
+ public let identifier = "AbsoluteReleaseReadinessPlugin"
36450
+ public let jsName = "AbsoluteReleaseReadiness"
36451
+ public let pluginMethods: [CAPPluginMethod] = [
36452
+ CAPPluginMethod(name: "ready", returnType: CAPPluginReturnPromise)
36453
+ ]
36454
+
36455
+ @objc public func ready(_ call: CAPPluginCall) {
36456
+ let info = Bundle.main.infoDictionary ?? [:]
36457
+ let version = info["CFBundleShortVersionString"] as? String ?? "unknown"
36458
+ let build = info["CFBundleVersion"] as? String ?? "unknown"
36459
+ NSLog("AbsoluteJS: Capacitor embedded web content ready; version=%@; build=%@", version, build)
36460
+ call.resolve(["ready": true])
36461
+ }
36462
+ }
36463
+ ${IOS_PLUGIN_END}
35964
36464
  `;
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;
36465
+ var replaceIosPluginRegion = (appDelegate, existingStart, existingEnd) => {
36466
+ const from = appDelegate.lastIndexOf(`
36467
+ `, existingStart) + 1;
36468
+ const newline = appDelegate.indexOf(`
36469
+ `, existingEnd + IOS_PLUGIN_END.length);
36470
+ const through = newline < 0 ? appDelegate.length : newline + 1;
36471
+ return `${appDelegate.slice(0, from)}${iosPluginRegion3}${appDelegate.slice(through)}`;
35976
36472
  };
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-"));
36473
+ var configureIos5 = async (config) => {
36474
+ const appDelegatePath = join31(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
36475
+ const capacitorConfigPath = join31(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
36476
+ const appDelegate = await readFile28(appDelegatePath, "utf8");
36477
+ const existingStart = appDelegate.indexOf(IOS_PLUGIN_START);
36478
+ const existingEnd = appDelegate.indexOf(IOS_PLUGIN_END);
36479
+ if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
36480
+ throw new TypeError("AbsoluteJS iOS release-readiness markers are malformed.");
36481
+ const source = existingStart < 0 ? `${appDelegate.trimEnd()}
36482
+
36483
+ ${iosPluginRegion3}` : replaceIosPluginRegion(appDelegate, existingStart, existingEnd);
36484
+ let capacitorConfig;
36004
36485
  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 });
36486
+ capacitorConfig = JSON.parse(await readFile28(capacitorConfigPath, "utf8"));
36487
+ } catch {
36488
+ throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting release readiness.");
36020
36489
  }
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")));
36490
+ if (typeof capacitorConfig !== "object" || capacitorConfig === null)
36491
+ throw new TypeError("iOS capacitor.config.json must contain an object.");
36492
+ const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
36493
+ if (!Array.isArray(packageClassList))
36494
+ throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting release readiness.");
36495
+ const classes = packageClassList.filter((value) => typeof value === "string");
36496
+ const nextConfig = `${JSON.stringify({
36497
+ ...capacitorConfig,
36498
+ packageClassList: [
36499
+ ...new Set([...classes, "AbsoluteReleaseReadinessPlugin"])
36500
+ ]
36501
+ }, null, "\t")}
36502
+ `;
36503
+ const changed = await Promise.all([
36504
+ writeChanged3(appDelegatePath, source),
36505
+ writeChanged3(capacitorConfigPath, nextConfig)
36506
+ ]);
36507
+ return changed.some(Boolean);
36104
36508
  };
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;
36509
+ var configureAndroid5 = async (config) => {
36510
+ const mainActivityPath = await activityPath(config);
36511
+ const activity = await readFile28(mainActivityPath, "utf8");
36512
+ const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
36513
+ if (!packageName)
36514
+ throw new TypeError("Android MainActivity package declaration was not found.");
36515
+ const pluginPath = join31(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN3);
36516
+ const changed = await Promise.all([
36517
+ writeChanged3(mainActivityPath, injectRegistration(activity)),
36518
+ writeChanged3(pluginPath, pluginSource(packageName))
36519
+ ]);
36520
+ return changed.some(Boolean);
36114
36521
  };
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;
36522
+ var applyAbsoluteNativeReleaseReadiness = async (config, platforms = config.platforms) => {
36523
+ if (config.engine !== "capacitor")
36524
+ return { changed: false };
36525
+ const changed = await Promise.all([
36526
+ platforms.includes("android") ? configureAndroid5(config) : false,
36527
+ platforms.includes("ios") ? configureIos5(config) : false
36528
+ ]);
36529
+ return { changed: changed.some(Boolean) };
36124
36530
  };
36531
+
36532
+ // src/mobile/index.ts
36533
+ init_pageProtocol();
36534
+ init_producerContext();
36535
+ init_releaseArtifact();
36536
+
36125
36537
  // 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";
36538
+ import { access as access20 } from "fs/promises";
36539
+ import { isAbsolute as isAbsolute12, relative as relative19, resolve as resolve21, sep as sep14 } from "path";
36128
36540
  import { pathToFileURL as pathToFileURL3 } from "url";
36129
36541
  var prepareAbsoluteIosRelease = async (publisher, options) => {
36130
36542
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -36158,24 +36570,24 @@ var requireCertificationReceipt = (publication, certification, requirement, rele
36158
36570
  if (verification && (!receipt.provenance || receipt.provenance.subject !== releaseId))
36159
36571
  throw new TypeError("Native release registry did not retain trusted certification provenance.");
36160
36572
  };
36161
- var isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36162
- var isPublisher = (value) => isRecord16(value) && typeof value.publish === "function";
36573
+ var isRecord17 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
36574
+ var isPublisher = (value) => isRecord17(value) && typeof value.publish === "function";
36163
36575
  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)) {
36576
+ const root = resolve21(projectRoot);
36577
+ const path = resolve21(root, requested);
36578
+ const projectRelative = relative19(root, path);
36579
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep14}`) || isAbsolute12(projectRelative)) {
36168
36580
  throw new TypeError("mobile publish --registry must remain inside the project.");
36169
36581
  }
36170
36582
  return path;
36171
36583
  };
36172
36584
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
36173
36585
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
36174
- await access19(modulePath).catch(() => {
36586
+ await access20(modulePath).catch(() => {
36175
36587
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
36176
36588
  });
36177
36589
  const loaded = await import(pathToFileURL3(modulePath).href);
36178
- const publisher = isRecord16(loaded) ? loaded.default ?? loaded.registry : undefined;
36590
+ const publisher = isRecord17(loaded) ? loaded.default ?? loaded.registry : undefined;
36179
36591
  if (!isPublisher(publisher)) {
36180
36592
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
36181
36593
  }
@@ -36257,7 +36669,7 @@ var publishAbsoluteIosRelease = async (options) => {
36257
36669
  init_pageProtocol();
36258
36670
  init_releaseArtifact();
36259
36671
  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";
36672
+ import { dirname as dirname22, extname as extname6, relative as relative20, resolve as resolve22 } from "path";
36261
36673
  import ts2 from "typescript";
36262
36674
  var ROUTE_METHODS = new Set(["get", "head"]);
36263
36675
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
@@ -36308,7 +36720,7 @@ var PAGE_HANDLERS = new Map([
36308
36720
  ]
36309
36721
  ]);
36310
36722
  var posixPath = (value) => value.replace(/\\/g, "/");
36311
- var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname21(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
36723
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname22(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
36312
36724
  var createProgram = (entry, projectRoot) => {
36313
36725
  const configPath2 = findTsconfig(entry, projectRoot);
36314
36726
  if (!configPath2) {
@@ -36320,7 +36732,7 @@ var createProgram = (entry, projectRoot) => {
36320
36732
  target: ts2.ScriptTarget.ESNext
36321
36733
  });
36322
36734
  }
36323
- const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname21(configPath2));
36735
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync8(path, "utf8")).config, ts2.sys, dirname22(configPath2));
36324
36736
  if (!parsed.fileNames.includes(entry))
36325
36737
  parsed.fileNames.push(entry);
36326
36738
  return ts2.createProgram(parsed.fileNames, parsed.options);
@@ -36426,7 +36838,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
36426
36838
  const declaration = symbol?.declarations?.[0];
36427
36839
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
36428
36840
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
36429
- const source = posixPath(relative19(projectRoot, file));
36841
+ const source = posixPath(relative20(projectRoot, file));
36430
36842
  return `${source}#${exportedName}`;
36431
36843
  };
36432
36844
  var resolveAlias = (symbol, checker) => {
@@ -36665,7 +37077,7 @@ var analyzeProgram = (program, projectRoot) => {
36665
37077
  const checker = program.getTypeChecker();
36666
37078
  const analyzed = new Map;
36667
37079
  for (const sourceFile of program.getSourceFiles()) {
36668
- const resolvedFile = resolve21(sourceFile.fileName);
37080
+ const resolvedFile = resolve22(sourceFile.fileName);
36669
37081
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
36670
37082
  continue;
36671
37083
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -36758,14 +37170,14 @@ var transformFile = (source, fileName, analysis) => {
36758
37170
  };
36759
37171
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
36760
37172
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
36761
- const projectRoot = resolve21(options.projectRoot ?? process.cwd());
36762
- const entry = resolve21(options.entry);
37173
+ const projectRoot = resolve22(options.projectRoot ?? process.cwd());
37174
+ const entry = resolve22(options.entry);
36763
37175
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
36764
37176
  return {
36765
37177
  name: "absolute-mobile-route-metadata",
36766
37178
  setup(build) {
36767
37179
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
36768
- const analysis = analyzed.get(resolve21(path));
37180
+ const analysis = analyzed.get(resolve22(path));
36769
37181
  if (!analysis)
36770
37182
  return;
36771
37183
  const source = await Bun.file(path).text();
@@ -36778,11 +37190,11 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
36778
37190
  };
36779
37191
  };
36780
37192
  var inspectAbsoluteMobileRouteMetadata = (options) => {
36781
- const projectRoot = resolve21(options.projectRoot ?? process.cwd());
36782
- const entry = resolve21(options.entry);
37193
+ const projectRoot = resolve22(options.projectRoot ?? process.cwd());
37194
+ const entry = resolve22(options.entry);
36783
37195
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
36784
37196
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
36785
- file: posixPath(relative19(projectRoot, file)),
37197
+ file: posixPath(relative20(projectRoot, file)),
36786
37198
  metadata
36787
37199
  })));
36788
37200
  };
@@ -37116,7 +37528,7 @@ var createCapacitor = (win) => {
37116
37528
  callbackId
37117
37529
  }, callback);
37118
37530
  };
37119
- const p = new Promise((resolve22) => call.then(() => resolve22({ remove })));
37531
+ const p = new Promise((resolve23) => call.then(() => resolve23({ remove })));
37120
37532
  p.remove = async () => {
37121
37533
  console.warn(`Using addListener() without 'await' is deprecated.`);
37122
37534
  await remove();
@@ -37321,11 +37733,11 @@ class CapacitorCookiesPluginWeb extends WebPlugin {
37321
37733
  var CapacitorCookies = registerPlugin("CapacitorCookies", {
37322
37734
  web: () => new CapacitorCookiesPluginWeb
37323
37735
  });
37324
- var readBlobAsBase64 = async (blob) => new Promise((resolve22, reject) => {
37736
+ var readBlobAsBase64 = async (blob) => new Promise((resolve23, reject) => {
37325
37737
  const reader = new FileReader;
37326
37738
  reader.onload = () => {
37327
37739
  const base64String = reader.result;
37328
- resolve22(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
37740
+ resolve23(base64String.indexOf(",") >= 0 ? base64String.split(",")[1] : base64String);
37329
37741
  };
37330
37742
  reader.onerror = (error) => reject(error);
37331
37743
  reader.readAsDataURL(blob);
@@ -37501,7 +37913,7 @@ var REPORT_KINDS = new Set([
37501
37913
  "signal"
37502
37914
  ]);
37503
37915
  var nativeObservability = registerPlugin("AbsoluteMobileObservability");
37504
- var isRecord17 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
37916
+ var isRecord18 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
37505
37917
  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
37918
  var redactValue = (value, depth = 0) => {
37507
37919
  if (depth >= MAX_REDACTION_DEPTH)
@@ -37512,7 +37924,7 @@ var redactValue = (value, depth = 0) => {
37512
37924
  return value;
37513
37925
  if (Array.isArray(value))
37514
37926
  return value.slice(0, 64).map((entry) => redactValue(entry, depth + 1));
37515
- if (!isRecord17(value))
37927
+ if (!isRecord18(value))
37516
37928
  return String(value).slice(0, MAX_STRING_LENGTH);
37517
37929
  return Object.fromEntries(Object.entries(value).slice(0, 128).map(([key, entry]) => [
37518
37930
  key,
@@ -37520,7 +37932,7 @@ var redactValue = (value, depth = 0) => {
37520
37932
  ]));
37521
37933
  };
37522
37934
  var parseReport = (value) => {
37523
- if (!isRecord17(value) || !isRecord17(value.details))
37935
+ if (!isRecord18(value) || !isRecord18(value.details))
37524
37936
  return;
37525
37937
  if (typeof value.id !== "string" || !REPORT_ID.test(value.id))
37526
37938
  return;
@@ -37531,7 +37943,7 @@ var parseReport = (value) => {
37531
37943
  if (value.platform !== "android" && value.platform !== "ios")
37532
37944
  return;
37533
37945
  const details = redactValue(value.details);
37534
- if (!isRecord17(details))
37946
+ if (!isRecord18(details))
37535
37947
  return;
37536
37948
  if (new TextEncoder().encode(JSON.stringify(details)).byteLength > MAX_DETAIL_BYTES)
37537
37949
  return;
@@ -41985,8 +42397,8 @@ var createBeacon = (options) => {
41985
42397
  if (await fontAvailable(element))
41986
42398
  return;
41987
42399
  if (confirmationMs > 0) {
41988
- await new Promise((resolve22) => {
41989
- const timer2 = window.setTimeout(resolve22, confirmationMs);
42400
+ await new Promise((resolve23) => {
42401
+ const timer2 = window.setTimeout(resolve23, confirmationMs);
41990
42402
  cleanups.push(() => window.clearTimeout(timer2));
41991
42403
  });
41992
42404
  if (document.visibilityState === "hidden")
@@ -42974,51 +43386,51 @@ var createAbsoluteMobileUpdateClient = (options) => {
42974
43386
  };
42975
43387
  };
42976
43388
  // 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";
43389
+ import { access as access21 } from "fs/promises";
43390
+ import { isAbsolute as isAbsolute13, relative as relative22, resolve as resolve24, sep as sep15 } from "path";
42979
43391
  import { pathToFileURL as pathToFileURL4 } from "url";
42980
43392
 
42981
43393
  // src/mobile/updateSigning.ts
42982
43394
  import { createHash as createHash17, sign, verify } from "crypto";
42983
43395
  import {
42984
43396
  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
43397
+ mkdir as mkdir23,
43398
+ mkdtemp as mkdtemp12,
43399
+ readdir as readdir11,
43400
+ readFile as readFile29,
43401
+ rename as rename20,
43402
+ rm as rm17,
43403
+ stat as stat8,
43404
+ writeFile as writeFile25
42993
43405
  } from "fs/promises";
42994
- import { dirname as dirname22, join as join31, relative as relative20, resolve as resolve22 } from "path";
43406
+ import { dirname as dirname23, join as join32, relative as relative21, resolve as resolve23 } from "path";
42995
43407
  var UPDATE_MANIFEST_FILE = "update.json";
42996
43408
  var UPDATE_FILES_DIRECTORY = "files";
42997
43409
  var sha2565 = (value) => createHash17("sha256").update(value).digest("hex");
42998
43410
  var listFiles = async (root, directory = root) => {
42999
- const entries = await readdir10(directory, { withFileTypes: true });
43411
+ const entries = await readdir11(directory, { withFileTypes: true });
43000
43412
  const paths = await Promise.all(entries.map(async (entry) => {
43001
- const path = join31(directory, entry.name);
43413
+ const path = join32(directory, entry.name);
43002
43414
  if (entry.isDirectory())
43003
43415
  return listFiles(root, path);
43004
43416
  if (!entry.isFile())
43005
43417
  throw new TypeError("Mobile updates cannot contain links or special files.");
43006
- return [relative20(root, path).replaceAll("\\", "/")];
43418
+ return [relative21(root, path).replaceAll("\\", "/")];
43007
43419
  }));
43008
43420
  return paths.flat().sort((left, right) => left.localeCompare(right));
43009
43421
  };
43010
43422
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
43011
- const source = join31(root, path);
43423
+ const source = join32(root, path);
43012
43424
  const [metadata, contents] = await Promise.all([
43013
- stat7(source),
43014
- readFile28(source)
43425
+ stat8(source),
43426
+ readFile29(source)
43015
43427
  ]);
43016
43428
  return { bytes: metadata.size, path, sha256: sha2565(contents) };
43017
43429
  }));
43018
43430
  var releaseIdFor = (value) => `amu_${sha2565(canonicalizeAbsoluteMobileUpdate(value))}`;
43019
43431
  var buildAbsoluteMobileUpdate = async (options) => {
43020
- const bundleDirectory = resolve22(options.bundleDirectory);
43021
- const outputRoot = resolve22(options.outputDirectory);
43432
+ const bundleDirectory = resolve23(options.bundleDirectory);
43433
+ const outputRoot = resolve23(options.outputDirectory);
43022
43434
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
43023
43435
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
43024
43436
  const paths = await listFiles(bundleDirectory);
@@ -43046,38 +43458,38 @@ var buildAbsoluteMobileUpdate = async (options) => {
43046
43458
  value: signature.toString("base64")
43047
43459
  }
43048
43460
  });
43049
- await mkdir22(outputRoot, { recursive: true });
43050
- const outputDirectory = join31(outputRoot, manifest.releaseId);
43051
- const staging = await mkdtemp11(join31(outputRoot, ".stage-"));
43461
+ await mkdir23(outputRoot, { recursive: true });
43462
+ const outputDirectory = join32(outputRoot, manifest.releaseId);
43463
+ const staging = await mkdtemp12(join32(outputRoot, ".stage-"));
43052
43464
  try {
43053
- await cp2(bundleDirectory, join31(staging, UPDATE_FILES_DIRECTORY), {
43465
+ await cp2(bundleDirectory, join32(staging, UPDATE_FILES_DIRECTORY), {
43054
43466
  force: true,
43055
43467
  recursive: true
43056
43468
  });
43057
- await writeFile24(join31(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
43469
+ await writeFile25(join32(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
43058
43470
  `);
43059
- await rename19(staging, outputDirectory);
43471
+ await rename20(staging, outputDirectory);
43060
43472
  } catch (error) {
43061
- await rm16(staging, { force: true, recursive: true });
43473
+ await rm17(staging, { force: true, recursive: true });
43062
43474
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
43063
43475
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
43064
43476
  throw error;
43065
43477
  }
43066
43478
  return {
43067
43479
  manifest,
43068
- manifestPath: join31(outputDirectory, UPDATE_MANIFEST_FILE),
43480
+ manifestPath: join32(outputDirectory, UPDATE_MANIFEST_FILE),
43069
43481
  outputDirectory
43070
43482
  };
43071
43483
  };
43072
43484
  var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) => {
43073
- const source = resolve22(updateDirectory, UPDATE_FILES_DIRECTORY, path);
43074
- const root = resolve22(updateDirectory, UPDATE_FILES_DIRECTORY);
43485
+ const source = resolve23(updateDirectory, UPDATE_FILES_DIRECTORY, path);
43486
+ const root = resolve23(updateDirectory, UPDATE_FILES_DIRECTORY);
43075
43487
  if (!source.startsWith(`${root}/`))
43076
43488
  throw new TypeError("Update file escaped its release.");
43077
- await mkdir22(dirname22(destination), { recursive: true });
43489
+ await mkdir23(dirname23(destination), { recursive: true });
43078
43490
  await cp2(source, destination, { force: true });
43079
43491
  };
43080
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile28(join31(resolve22(directory), UPDATE_MANIFEST_FILE), "utf8")));
43492
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile29(join32(resolve23(directory), UPDATE_MANIFEST_FILE), "utf8")));
43081
43493
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
43082
43494
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
43083
43495
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -43193,17 +43605,17 @@ var pruneAbsoluteMobileUpdates = async (options) => {
43193
43605
  };
43194
43606
  var object5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
43195
43607
  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))
43608
+ var projectPath3 = (projectRoot, requested, label) => {
43609
+ const root = resolve24(projectRoot);
43610
+ const path = resolve24(root, requested);
43611
+ const projectRelative = relative22(root, path);
43612
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep15}`) || isAbsolute13(projectRelative))
43201
43613
  throw new TypeError(`${label} must remain inside the project.`);
43202
43614
  return path;
43203
43615
  };
43204
43616
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
43205
- const modulePath = projectPath2(projectRoot, requestedModulePath, "mobile update registry");
43206
- await access20(modulePath).catch(() => {
43617
+ const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
43618
+ await access21(modulePath).catch(() => {
43207
43619
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
43208
43620
  });
43209
43621
  const loaded = await import(pathToFileURL4(modulePath).href);
@@ -43225,7 +43637,7 @@ var promoteAbsoluteMobileUpdate = async (options) => {
43225
43637
  return result;
43226
43638
  };
43227
43639
  var publishAbsoluteMobileUpdate = async (options) => {
43228
- const releaseDirectory = projectPath2(options.projectRoot, options.releaseDirectory, "mobile update release directory");
43640
+ const releaseDirectory = projectPath3(options.projectRoot, options.releaseDirectory, "mobile update release directory");
43229
43641
  const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
43230
43642
  const result = await options.publisher.publishUpdate({
43231
43643
  manifest,
@@ -43310,6 +43722,8 @@ export {
43310
43722
  ABSOLUTE_IOS_RELEASE_FORMAT,
43311
43723
  ABSOLUTE_IOS_SIMULATOR_NAME,
43312
43724
  ABSOLUTE_MOBILE_CERTIFICATION_VERIFICATION_FORMAT,
43725
+ ABSOLUTE_MOBILE_CI_PROMOTION_AUDIT_FORMAT,
43726
+ ABSOLUTE_MOBILE_CI_PROMOTION_CONTEXT_FORMAT,
43313
43727
  ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
43314
43728
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
43315
43729
  ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
@@ -43373,6 +43787,7 @@ export {
43373
43787
  assertAbsoluteAndroidInstalledAppUpgrade,
43374
43788
  assertAbsoluteDeviceCapabilityPackages,
43375
43789
  assertAbsoluteIosInstalledAppUpgrade,
43790
+ auditAbsoluteMobileCiPromotion,
43376
43791
  buildAbsoluteAndroidRelease,
43377
43792
  buildAbsoluteExpoAndroidWslBundle,
43378
43793
  buildAbsoluteIosRelease,
@@ -43436,6 +43851,7 @@ export {
43436
43851
  inspectAbsoluteAndroidInstalledApp,
43437
43852
  inspectAbsoluteBundletool,
43438
43853
  inspectAbsoluteIosInstalledApp,
43854
+ inspectAbsoluteMobileCiRun,
43439
43855
  inspectAbsoluteMobileRouteMetadata,
43440
43856
  inspectAbsoluteMobileUpdateHealth,
43441
43857
  inspectAbsoluteMobileUpdateRollout,
@@ -43488,9 +43904,11 @@ export {
43488
43904
  parseAbsoluteIosLogLine,
43489
43905
  parseAbsoluteMobileBuildPageMetadata,
43490
43906
  parseAbsoluteMobileCertificationVerification,
43907
+ parseAbsoluteMobileCiRunStatus,
43491
43908
  parseAbsoluteMobileCompatibilityArtifact,
43492
43909
  parseAbsoluteMobilePageEnvelope,
43493
43910
  parseAbsoluteMobilePageRequest,
43911
+ parseAbsoluteMobilePromotionContext,
43494
43912
  parseAbsoluteMobileUnsignedUpdateManifest,
43495
43913
  parseAbsoluteMobileUpdateManifest,
43496
43914
  parseAbsoluteMobileUpdateRequest,
@@ -43567,6 +43985,7 @@ export {
43567
43985
  verifyAbsoluteMobileReleaseCertification,
43568
43986
  verifyAbsoluteMobileUpdateSignature,
43569
43987
  waitForAbsoluteIosHmrLog,
43988
+ watchAbsoluteMobileCiRun,
43570
43989
  writeAbsoluteCapacitorConfig,
43571
43990
  writeAbsoluteExpoProject,
43572
43991
  writeAbsoluteMobileCertificationVerification,
@@ -43575,5 +43994,5 @@ export {
43575
43994
  writeAbsoluteMobileUpdateRegistry
43576
43995
  };
43577
43996
 
43578
- //# debugId=F05777F8BA048C6164756E2164756E21
43997
+ //# debugId=1FBA634844700C1464756E2164756E21
43579
43998
  //# sourceMappingURL=index.js.map