@absolutejs/absolute 0.20.0-beta.80 → 0.20.0-beta.81

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.
@@ -4,12 +4,12 @@ import {
4
4
  } from "./index-zh6hhrwy.js";
5
5
  import {
6
6
  start
7
- } from "./index-d4ar3mwd.js";
7
+ } from "./index-q78x1k9q.js";
8
8
  import {
9
9
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
10
10
  ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
11
11
  resolveAbsoluteMobileRoute
12
- } from "./index-w5y7wxn9.js";
12
+ } from "./index-8gksmws3.js";
13
13
  import"./index-bzs19w3r.js";
14
14
  import"./index-s2tazax7.js";
15
15
  import {
@@ -49,7 +49,7 @@ import {
49
49
  import"./index-w5vswatm.js";
50
50
  import {
51
51
  normalizeAbsoluteMobileConfig
52
- } from "./index-6fb7k6dg.js";
52
+ } from "./index-czw3jd8f.js";
53
53
  import {
54
54
  ABSOLUTE_MOBILE_UPDATE_FORMAT,
55
55
  absoluteDeviceNativeRequirements,
@@ -16768,15 +16768,15 @@ var require_main = __commonJS(function(exports) {
16768
16768
 
16769
16769
  // src/cli/scripts/mobile.ts
16770
16770
  import {
16771
- access as access11,
16772
- mkdir as mkdir11,
16771
+ access as access12,
16772
+ mkdir as mkdir12,
16773
16773
  mkdtemp as mkdtemp3,
16774
16774
  readFile as readFile16,
16775
16775
  rm as rm6,
16776
16776
  writeFile as writeFile14
16777
16777
  } from "fs/promises";
16778
- import { createPublicKey } from "crypto";
16779
- import { join as join13, relative as relative10, resolve as resolve11 } from "path";
16778
+ import { createPublicKey as createPublicKey2 } from "crypto";
16779
+ import { join as join13, relative as relative11, resolve as resolve12 } from "path";
16780
16780
  import { createInterface } from "readline/promises";
16781
16781
 
16782
16782
  // src/mobile/nativeDeepLinks.ts
@@ -19083,8 +19083,8 @@ var waitForAbsoluteAndroidHmrApply = async (session, options) => {
19083
19083
  };
19084
19084
 
19085
19085
  // src/mobile/releaseDoctor.ts
19086
- import { access as access3, readFile as readFile8, readdir as readdir3 } from "fs/promises";
19087
- import { dirname as dirname4, extname, join as join7, relative as relative2 } from "path";
19086
+ import { access as access4, readFile as readFile8, readdir as readdir3 } from "fs/promises";
19087
+ import { dirname as dirname5, extname, join as join7, relative as relative3 } from "path";
19088
19088
 
19089
19089
  // src/mobile/mobileBundleInspection.ts
19090
19090
  import { createHash } from "crypto";
@@ -19240,6 +19240,171 @@ var inspectAbsoluteMobileBundle = async (config, projectRoot) => {
19240
19240
  }
19241
19241
  };
19242
19242
 
19243
+ // src/mobile/updateServer.ts
19244
+ import {
19245
+ createPrivateKey,
19246
+ createPublicKey,
19247
+ X509Certificate
19248
+ } from "crypto";
19249
+ import { access as access3, mkdir as mkdir5 } from "fs/promises";
19250
+ import { dirname as dirname4, isAbsolute, relative as relative2, resolve as resolve4, sep } from "path";
19251
+ import { pathToFileURL } from "url";
19252
+ import { Elysia as Elysia2 } from "elysia";
19253
+ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1;
19254
+ var DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE = "mobile.update.ts";
19255
+ var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19256
+ var projectPath = (projectRoot, requested) => {
19257
+ const root = resolve4(projectRoot);
19258
+ const path = resolve4(root, requested);
19259
+ const projectRelative = relative2(root, path);
19260
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep}`) || isAbsolute(projectRelative))
19261
+ throw new TypeError("mobile.updates.server.registry must remain inside the project.");
19262
+ return path;
19263
+ };
19264
+ var isRegistry = (value) => object(value) && [
19265
+ "publishUpdate",
19266
+ "promoteUpdate",
19267
+ "rollbackUpdate",
19268
+ "resolveUpdate",
19269
+ "readUpdateFile"
19270
+ ].every((method) => typeof value[method] === "function");
19271
+ var serverMetadata = (value) => {
19272
+ if (!object(value))
19273
+ throw new TypeError("Mobile update registry must export valid absoluteMobileUpdateServer metadata. Run `absolute mobile update provision`.");
19274
+ const { format, provider, storage } = value;
19275
+ if (format !== ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT || storage !== "local" && storage !== "durable" || typeof provider !== "string" || !provider)
19276
+ throw new TypeError("Mobile update registry must export valid absoluteMobileUpdateServer metadata. Run `absolute mobile update provision`.");
19277
+ const metadata = {
19278
+ format: ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT,
19279
+ provider,
19280
+ storage
19281
+ };
19282
+ return metadata;
19283
+ };
19284
+ var loadAbsoluteMobileUpdateServerModule = async (projectRoot, requestedModulePath = DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE) => {
19285
+ const modulePath = projectPath(projectRoot, requestedModulePath);
19286
+ await access3(modulePath).catch(() => {
19287
+ throw new TypeError(`Mobile update server registry does not exist: ${modulePath}. Run \`absolute mobile update provision\`.`);
19288
+ });
19289
+ const loaded = await import(pathToFileURL(modulePath).href);
19290
+ if (!object(loaded))
19291
+ throw new TypeError("Mobile update registry module has no exports.");
19292
+ const registry = loaded.default ?? loaded.registry;
19293
+ if (!isRegistry(registry))
19294
+ throw new TypeError("Mobile update registry must implement publication, promotion, rollback, resolution, and file reads.");
19295
+ return {
19296
+ metadata: serverMetadata(loaded.absoluteMobileUpdateServer),
19297
+ registry
19298
+ };
19299
+ };
19300
+ var expoSigningOptions = (config) => {
19301
+ if (!config.updates?.expoCodeSigning)
19302
+ return;
19303
+ const entries = Object.entries(config.updateServer?.expoCodeSigningKeys ?? {});
19304
+ const keys = Object.fromEntries(entries.map(([keyId, key]) => {
19305
+ const privateKey = process.env[key.privateKeyEnv];
19306
+ if (!privateKey)
19307
+ throw new TypeError(`Expo update serving requires ${key.privateKeyEnv} on the trusted server.`);
19308
+ try {
19309
+ const certificate = new X509Certificate(key.certificatePem);
19310
+ const expected = certificate.publicKey.export({
19311
+ format: "der",
19312
+ type: "spki"
19313
+ });
19314
+ const actual = createPublicKey(createPrivateKey(privateKey)).export({
19315
+ format: "der",
19316
+ type: "spki"
19317
+ });
19318
+ if (!expected.equals(actual))
19319
+ throw new Error("key mismatch");
19320
+ } catch (error) {
19321
+ throw new TypeError(`${key.privateKeyEnv} must contain the RSA private key matching Expo update key ${keyId}.`, { cause: error });
19322
+ }
19323
+ return [keyId, { certificate: key.certificatePem, privateKey }];
19324
+ }));
19325
+ return { keys };
19326
+ };
19327
+ var inspectAbsoluteMobileUpdateServer = async (config, projectRoot) => {
19328
+ if (!config.updates)
19329
+ return;
19330
+ const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, config.updateServer?.registryModule);
19331
+ if (module.metadata.storage !== "durable")
19332
+ throw new TypeError("Mobile production updates require durable object storage; the configured registry is local-only.");
19333
+ if (config.engine === "expo")
19334
+ expoSigningOptions(config);
19335
+ return module.metadata;
19336
+ };
19337
+ var publicKeysSource = (publicKeys) => JSON.stringify(publicKeys, null, "\t");
19338
+ var renderAbsoluteMobileUpdateRegistry = (options) => {
19339
+ const metadata = `export const absoluteMobileUpdateServer = {
19340
+ format: 1,
19341
+ provider: '${options.storage}',
19342
+ storage: '${options.storage === "local" ? "local" : "durable"}'
19343
+ } as const;`;
19344
+ if (options.storage === "local")
19345
+ return `import { fileURLToPath } from 'node:url';
19346
+ import { localBlobStore } from '@absolutejs/blob/local';
19347
+ import { createMobileUpdateRegistry } from '@absolutejs/deploy/mobile-update';
19348
+
19349
+ ${metadata}
19350
+
19351
+ const store = localBlobStore({
19352
+ root: process.env.ABSOLUTE_MOBILE_UPDATE_LOCAL_ROOT ??
19353
+ fileURLToPath(new URL('./.absolutejs/mobile/update-registry/', import.meta.url))
19354
+ });
19355
+
19356
+ export default createMobileUpdateRegistry({
19357
+ publicKeys: ${publicKeysSource(options.publicKeys)},
19358
+ store
19359
+ });
19360
+ `;
19361
+ return `import { S3Client } from '@aws-sdk/client-s3';
19362
+ import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
19363
+ import { createMobileUpdateRegistry } from '@absolutejs/deploy/mobile-update';
19364
+
19365
+ ${metadata}
19366
+
19367
+ const required = (name: string) => {
19368
+ const value = process.env[name];
19369
+ if (!value) throw new Error(\`Missing \${name}\`);
19370
+ return value;
19371
+ };
19372
+
19373
+ const client = new S3Client({
19374
+ region: process.env.ABSOLUTE_MOBILE_UPDATE_S3_REGION ?? 'auto',
19375
+ forcePathStyle: process.env.ABSOLUTE_MOBILE_UPDATE_S3_FORCE_PATH_STYLE === '1',
19376
+ ...(process.env.ABSOLUTE_MOBILE_UPDATE_S3_ENDPOINT
19377
+ ? { endpoint: process.env.ABSOLUTE_MOBILE_UPDATE_S3_ENDPOINT }
19378
+ : {})
19379
+ });
19380
+ const store = awsS3BlobStore({
19381
+ bucket: required('ABSOLUTE_MOBILE_UPDATE_S3_BUCKET'),
19382
+ client
19383
+ });
19384
+
19385
+ export default createMobileUpdateRegistry({
19386
+ publicKeys: ${publicKeysSource(options.publicKeys)},
19387
+ store
19388
+ });
19389
+ `;
19390
+ };
19391
+ var writeAbsoluteMobileUpdateRegistry = async (options) => {
19392
+ const path = projectPath(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
19393
+ if (!options.force) {
19394
+ await access3(path).then(() => {
19395
+ throw new TypeError(`Mobile update registry already exists: ${path}. Pass --force to replace it.`);
19396
+ }, () => {
19397
+ return;
19398
+ });
19399
+ }
19400
+ await mkdir5(dirname4(path), { recursive: true });
19401
+ await Bun.write(path, renderAbsoluteMobileUpdateRegistry({
19402
+ publicKeys: options.publicKeys,
19403
+ storage: options.storage
19404
+ }));
19405
+ return path;
19406
+ };
19407
+
19243
19408
  // src/mobile/releaseDoctor.ts
19244
19409
  var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1;
19245
19410
  var HMR_ASSET_PATTERN = /(?:__HMR_WS__|hmr-timing|__absolute_target|absolutejs-error-overlay)/u;
@@ -19262,7 +19427,7 @@ var MANUAL_REVIEW = [
19262
19427
  ];
19263
19428
  var pathExists2 = async (path) => {
19264
19429
  try {
19265
- await access3(path);
19430
+ await access4(path);
19266
19431
  return true;
19267
19432
  } catch {
19268
19433
  return false;
@@ -19298,6 +19463,16 @@ var warn = (id, detail, path, remediation) => ({
19298
19463
  remediation,
19299
19464
  status: "warn"
19300
19465
  });
19466
+ var mobileUpdateServerCheck = async (config, projectRoot) => {
19467
+ if (!config.updates)
19468
+ return;
19469
+ try {
19470
+ const metadata = await inspectAbsoluteMobileUpdateServer(config, projectRoot);
19471
+ return pass("updates.trusted-server", `The trusted update server uses durable ${metadata?.provider ?? "object"} storage, supports publish/promote/rollback, and has valid server-only signing material.`, config.updateServer?.registryModule);
19472
+ } catch (error) {
19473
+ return fail("updates.trusted-server", error instanceof Error ? error.message : "The trusted mobile update server is not release-ready.", config.updateServer?.registryModule ?? "mobile.update.ts", "Run `absolute mobile update provision --storage s3 --force`, provision its environment variables on the trusted server, and rerun the release doctor.");
19474
+ }
19475
+ };
19301
19476
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
19302
19477
  var readJsonObject = async (path) => {
19303
19478
  const value = JSON.parse(await readFile8(path, "utf8"));
@@ -19445,7 +19620,7 @@ var manifestReleaseCheck = async (manifestPath) => {
19445
19620
  const source = await readFile8(manifestPath, "utf8");
19446
19621
  const cleartext = /android:usesCleartextTraffic=["']true["']/u.test(source);
19447
19622
  const networkConfigName = source.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
19448
- const networkConfigPath = networkConfigName ? join7(dirname4(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
19623
+ const networkConfigPath = networkConfigName ? join7(dirname5(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
19449
19624
  const developmentTrustReference = /android:networkSecurityConfig=["']@xml\/absolutejs_dev_network_security["']/u.test(source);
19450
19625
  const developmentTrustContents = networkConfigPath ? await readFile8(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
19451
19626
  const developmentTrust = developmentTrustReference || developmentTrustContents;
@@ -19657,7 +19832,7 @@ var nativeObservabilityProjectionCheck = async (config, platform) => {
19657
19832
  return registration && plugin ? pass("android.native-observability", "The Android process-exit collector is projected and registered.", plugin) : fail("android.native-observability", "The Android process-exit collector is missing or unregistered.", plugin ?? sourceRoot, "Run `absolute mobile sync android` before building the release.");
19658
19833
  };
19659
19834
  var iosNativeSecurityCheck = async (iosRoot) => {
19660
- const applicationFiles = async (extensions) => (await sourceFiles(iosRoot, extensions)).filter((path) => !relative2(iosRoot, path).split(/[\\/]/u).some((part) => ["Pods", "DerivedData", "build"].includes(part)));
19835
+ const applicationFiles = async (extensions) => (await sourceFiles(iosRoot, extensions)).filter((path) => !relative3(iosRoot, path).split(/[\\/]/u).some((part) => ["Pods", "DerivedData", "build"].includes(part)));
19661
19836
  const entitlementPaths = await applicationFiles(new Set([".entitlements"]));
19662
19837
  const unsafeEntitlements = await containsPattern(entitlementPaths, /<key>(?:com\.apple\.security\.)?get-task-allow<\/key>\s*<true\s*\/>/u);
19663
19838
  if (unsafeEntitlements)
@@ -19671,12 +19846,12 @@ var iosNativeSecurityCheck = async (iosRoot) => {
19671
19846
  var iosDeepLinkProjectionCheck = async (config, iosRoot) => {
19672
19847
  const infoPath = join7(iosRoot, "App/App/Info.plist");
19673
19848
  const entitlementsPath = join7(iosRoot, "App/AbsoluteJS.entitlements");
19674
- const projectPath = join7(iosRoot, "App/App.xcodeproj/project.pbxproj");
19849
+ const projectPath2 = join7(iosRoot, "App/App.xcodeproj/project.pbxproj");
19675
19850
  try {
19676
19851
  const [info, entitlements, project] = await Promise.all([
19677
19852
  readFile8(infoPath, "utf8"),
19678
19853
  readFile8(entitlementsPath, "utf8"),
19679
- readFile8(projectPath, "utf8")
19854
+ readFile8(projectPath2, "utf8")
19680
19855
  ]);
19681
19856
  if (config.deepLinkScheme && (!info.includes("<key>CFBundleURLTypes</key>") || !info.includes(`<string>${config.deepLinkScheme}</string>`)))
19682
19857
  throw new TypeError("iOS custom URL scheme does not match mobile config.");
@@ -19761,8 +19936,8 @@ var expoIosPushCapabilityCheck = async (iosRoot, requirements) => {
19761
19936
  };
19762
19937
  var expoIosCapabilityProjectionCheck = async (config, requirements) => {
19763
19938
  const iosRoot = join7(config.nativeProjectDirectory, "ios");
19764
- const projectPath = await uniqueExpoIosFile(iosRoot, "**/*.xcodeproj/project.pbxproj", "Xcode project");
19765
- const project = await readFile8(projectPath, "utf8");
19939
+ const projectPath2 = await uniqueExpoIosFile(iosRoot, "**/*.xcodeproj/project.pbxproj", "Xcode project");
19940
+ const project = await readFile8(projectPath2, "utf8");
19766
19941
  const privacy = await expoIosPrivacyCapabilityCheck(iosRoot, project, requirements);
19767
19942
  if (privacy)
19768
19943
  return privacy;
@@ -19780,10 +19955,10 @@ var iosCapabilityProjectionCheck = async (config, requirements) => {
19780
19955
  return fail("mobile.device-capabilities", "iOS system-bar capability is missing its required view-controller setting.", infoPath, "Run `absolute mobile sync ios` to regenerate native capability settings.");
19781
19956
  if (requirements.iosPrivacyAccessedApis.length > 0) {
19782
19957
  const privacyPath = join7(appRoot, "PrivacyInfo.xcprivacy");
19783
- const projectPath = join7(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
19958
+ const projectPath2 = join7(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
19784
19959
  const [privacy, project] = await Promise.all([
19785
19960
  readFile8(privacyPath, "utf8").catch(() => ""),
19786
- readFile8(projectPath, "utf8").catch(() => "")
19961
+ readFile8(projectPath2, "utf8").catch(() => "")
19787
19962
  ]);
19788
19963
  const missing = requirements.iosPrivacyAccessedApis.some(({ api, reasons }) => !privacy.includes(`<string>${api}</string>`) || reasons.some((reason) => !privacy.includes(`<string>${reason}</string>`)));
19789
19964
  if (missing || !project.includes("PrivacyInfo.xcprivacy in Resources"))
@@ -19856,7 +20031,7 @@ var inspectAndroidRelease = async (config, projectRoot) => {
19856
20031
  ]);
19857
20032
  return checks.filter((check) => check !== undefined).map((check) => ({
19858
20033
  ...check,
19859
- path: check.path ? relative2(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
20034
+ path: check.path ? relative3(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
19860
20035
  }));
19861
20036
  };
19862
20037
  var inspectExpoAndroidRelease = async (config, projectRoot) => {
@@ -19878,7 +20053,7 @@ var inspectExpoAndroidRelease = async (config, projectRoot) => {
19878
20053
  ]);
19879
20054
  return checks.filter((check) => check !== undefined).map((check) => ({
19880
20055
  ...check,
19881
- path: check.path ? relative2(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
20056
+ path: check.path ? relative3(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
19882
20057
  }));
19883
20058
  };
19884
20059
  var uniqueExpoIosFile = async (iosRoot, pattern, label) => {
@@ -19892,7 +20067,7 @@ var uniqueExpoIosFile = async (iosRoot, pattern, label) => {
19892
20067
  };
19893
20068
  var expoIosNativeProjectionCheck = async (config, iosRoot) => {
19894
20069
  try {
19895
- const [infoPath, entitlementsPath, projectPath] = await Promise.all([
20070
+ const [infoPath, entitlementsPath, projectPath2] = await Promise.all([
19896
20071
  uniqueExpoIosFile(iosRoot, "**/Info.plist", "Info.plist"),
19897
20072
  uniqueExpoIosFile(iosRoot, "**/*.entitlements", "entitlements"),
19898
20073
  uniqueExpoIosFile(iosRoot, "**/*.xcodeproj/project.pbxproj", "Xcode project")
@@ -19900,7 +20075,7 @@ var expoIosNativeProjectionCheck = async (config, iosRoot) => {
19900
20075
  const [info, entitlements, project] = await Promise.all([
19901
20076
  readFile8(infoPath, "utf8"),
19902
20077
  readFile8(entitlementsPath, "utf8"),
19903
- readFile8(projectPath, "utf8")
20078
+ readFile8(projectPath2, "utf8")
19904
20079
  ]);
19905
20080
  if (/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(info))
19906
20081
  throw new TypeError("Expo iOS App Transport Security permits arbitrary network loads.");
@@ -19936,7 +20111,7 @@ var inspectExpoIosRelease = async (config, projectRoot) => {
19936
20111
  checks.push(nativeObservability);
19937
20112
  return checks.map((check) => ({
19938
20113
  ...check,
19939
- path: check.path ? relative2(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
20114
+ path: check.path ? relative3(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
19940
20115
  }));
19941
20116
  };
19942
20117
  var inspectIosRelease = async (config, projectRoot) => {
@@ -19978,7 +20153,7 @@ var inspectIosRelease = async (config, projectRoot) => {
19978
20153
  checks.push(updateWatchdog);
19979
20154
  return checks.map((check) => ({
19980
20155
  ...check,
19981
- path: check.path ? relative2(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
20156
+ path: check.path ? relative3(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
19982
20157
  }));
19983
20158
  };
19984
20159
  var createAbsoluteMobileComplianceReport = (config, result) => {
@@ -20006,11 +20181,12 @@ var inspectAbsoluteMobileRelease = async (config, projectRoot) => {
20006
20181
  Promise.resolve(productionOriginCheck(config, projectRoot)),
20007
20182
  Promise.resolve(associationIdentityCheck(config, projectRoot)),
20008
20183
  dependencyLockCheck(projectRoot),
20009
- config.engine === "expo" ? expoVersionCheck(config) : capacitorVersionCheck(config, projectRoot)
20184
+ config.engine === "expo" ? expoVersionCheck(config) : capacitorVersionCheck(config, projectRoot),
20185
+ mobileUpdateServerCheck(config, projectRoot)
20010
20186
  ]);
20011
- const checks = globalChecks.map((check) => ({
20187
+ const checks = globalChecks.filter((check) => check !== undefined).map((check) => ({
20012
20188
  ...check,
20013
- path: check.path ? relative2(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
20189
+ path: check.path ? relative3(projectRoot, check.path).replaceAll("\\", "/") || "." : undefined
20014
20190
  }));
20015
20191
  if (config.platforms.includes("android"))
20016
20192
  checks.push(...config.engine === "expo" ? await inspectExpoAndroidRelease(config, projectRoot) : await inspectAndroidRelease(config, projectRoot));
@@ -20020,13 +20196,13 @@ var inspectAbsoluteMobileRelease = async (config, projectRoot) => {
20020
20196
  if (syncSchema) {
20021
20197
  checks.push({
20022
20198
  ...syncSchema,
20023
- path: syncSchema.path ? relative2(projectRoot, syncSchema.path).replaceAll("\\", "/") || "." : undefined
20199
+ path: syncSchema.path ? relative3(projectRoot, syncSchema.path).replaceAll("\\", "/") || "." : undefined
20024
20200
  });
20025
20201
  }
20026
20202
  const deviceCapabilities = await deviceCapabilityReleaseCheck(config, projectRoot);
20027
20203
  checks.push({
20028
20204
  ...deviceCapabilities,
20029
- path: deviceCapabilities.path ? relative2(projectRoot, deviceCapabilities.path).replaceAll("\\", "/") || "." : undefined
20205
+ path: deviceCapabilities.path ? relative3(projectRoot, deviceCapabilities.path).replaceAll("\\", "/") || "." : undefined
20030
20206
  });
20031
20207
  return {
20032
20208
  checks,
@@ -20037,9 +20213,9 @@ var inspectAbsoluteMobileRelease = async (config, projectRoot) => {
20037
20213
  // src/mobile/androidRelease.ts
20038
20214
  import { createHash as createHash2 } from "crypto";
20039
20215
  import {
20040
- access as access4,
20216
+ access as access5,
20041
20217
  copyFile,
20042
- mkdir as mkdir5,
20218
+ mkdir as mkdir6,
20043
20219
  mkdtemp,
20044
20220
  readFile as readFile9,
20045
20221
  realpath,
@@ -20048,7 +20224,7 @@ import {
20048
20224
  stat as stat2,
20049
20225
  writeFile as writeFile8
20050
20226
  } from "fs/promises";
20051
- import { dirname as dirname5, isAbsolute, join as join8, relative as relative3, resolve as resolve4, sep } from "path";
20227
+ import { dirname as dirname6, isAbsolute as isAbsolute2, join as join8, relative as relative4, resolve as resolve5, sep as sep2 } from "path";
20052
20228
  var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1;
20053
20229
  var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20054
20230
  var requireManifest = (value) => {
@@ -20063,7 +20239,7 @@ var requireManifest = (value) => {
20063
20239
  };
20064
20240
  var pathExists3 = async (path) => {
20065
20241
  try {
20066
- await access4(path);
20242
+ await access5(path);
20067
20243
  return true;
20068
20244
  } catch {
20069
20245
  return false;
@@ -20133,10 +20309,10 @@ var fingerprintExpoAndroidProject = async (nativeDirectory) => {
20133
20309
  return createHash2("sha256").update(records.join("")).digest("hex");
20134
20310
  };
20135
20311
  var safeOutputDirectory = (projectRoot, requested) => {
20136
- const root = resolve4(projectRoot);
20137
- const output = resolve4(root, requested ?? ".absolutejs/mobile/releases/android");
20138
- const projectRelative = relative3(root, output);
20139
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep}`) || isAbsolute(projectRelative)) {
20312
+ const root = resolve5(projectRoot);
20313
+ const output = resolve5(root, requested ?? ".absolutejs/mobile/releases/android");
20314
+ const projectRelative = relative4(root, output);
20315
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep2}`) || isAbsolute2(projectRelative)) {
20140
20316
  throw new TypeError("mobile build --outdir must remain inside the project.");
20141
20317
  }
20142
20318
  return output;
@@ -20156,8 +20332,8 @@ var installRelease = async (artifactPath, metadata, outputRoot) => {
20156
20332
  }
20157
20333
  return { artifactPath: destination, metadata: existing, releaseRoot };
20158
20334
  }
20159
- await mkdir5(dirname5(releaseRoot), { recursive: true });
20160
- const staging = await mkdtemp(join8(dirname5(releaseRoot), ".android-stage-"));
20335
+ await mkdir6(dirname6(releaseRoot), { recursive: true });
20336
+ const staging = await mkdtemp(join8(dirname6(releaseRoot), ".android-stage-"));
20161
20337
  try {
20162
20338
  await copyFile(artifactPath, join8(staging, artifactName));
20163
20339
  const complete = {
@@ -20191,7 +20367,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
20191
20367
  if (options.versionCode !== undefined && (!Number.isSafeInteger(options.versionCode) || options.versionCode < 1 || options.versionCode > 2100000000)) {
20192
20368
  throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
20193
20369
  }
20194
- const projectRoot = resolve4(options.projectRoot);
20370
+ const projectRoot = resolve5(options.projectRoot);
20195
20371
  const host = options.host ?? detectAbsoluteMobileHost();
20196
20372
  if (options.config.engine === "expo" && host === "wsl") {
20197
20373
  throw new TypeError("Expo Android production builds from WSL are not available yet. Run the generated CI workflow on Linux or build from native Windows while the WSL projection is completed.");
@@ -20369,7 +20545,7 @@ var waitForAbsoluteIosHmrLog = async (options) => {
20369
20545
  };
20370
20546
 
20371
20547
  // src/mobile/nativeTestReport.ts
20372
- import { mkdir as mkdir6, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
20548
+ import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
20373
20549
  import { join as join9 } from "path";
20374
20550
  var secretPattern = /(authorization|access[_ -]?token|refresh[_ -]?token|socket[_ -]?ticket|password|cookie)(\s*[=:]\s*)([^\s,;]+)/giu;
20375
20551
  var bearerPattern = /bearer\s+[^\s,;]+/giu;
@@ -20518,7 +20694,7 @@ ${table(report.manualChecks)}
20518
20694
  `;
20519
20695
  };
20520
20696
  var writeAbsoluteNativeTestReport = async (directory, report) => {
20521
- await mkdir6(directory, { recursive: true });
20697
+ await mkdir7(directory, { recursive: true });
20522
20698
  const jsonPath = join9(directory, "report.json");
20523
20699
  const markdownPath = join9(directory, "report.md");
20524
20700
  await Promise.all([
@@ -20774,9 +20950,9 @@ var createAbsoluteAndroidTestReport = (options) => {
20774
20950
  };
20775
20951
 
20776
20952
  // src/mobile/releasePublisher.ts
20777
- import { access as access5 } from "fs/promises";
20778
- import { isAbsolute as isAbsolute2, relative as relative4, resolve as resolve5, sep as sep2 } from "path";
20779
- import { pathToFileURL } from "url";
20953
+ import { access as access6 } from "fs/promises";
20954
+ import { isAbsolute as isAbsolute3, relative as relative5, resolve as resolve6, sep as sep3 } from "path";
20955
+ import { pathToFileURL as pathToFileURL2 } from "url";
20780
20956
  var prepareAbsoluteIosRelease = async (publisher, options) => {
20781
20957
  if (typeof publisher.prepareIosRelease !== "function") {
20782
20958
  throw new TypeError("App Store Connect publishing requires a registry module created with @absolutejs/deploy/app-store-connect.");
@@ -20801,20 +20977,20 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
20801
20977
  var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20802
20978
  var isPublisher = (value) => isRecord5(value) && typeof value.publish === "function";
20803
20979
  var publisherModulePath = (projectRoot, requested) => {
20804
- const root = resolve5(projectRoot);
20805
- const path = resolve5(root, requested);
20806
- const projectRelative = relative4(root, path);
20807
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep2}`) || isAbsolute2(projectRelative)) {
20980
+ const root = resolve6(projectRoot);
20981
+ const path = resolve6(root, requested);
20982
+ const projectRelative = relative5(root, path);
20983
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep3}`) || isAbsolute3(projectRelative)) {
20808
20984
  throw new TypeError("mobile publish --registry must remain inside the project.");
20809
20985
  }
20810
20986
  return path;
20811
20987
  };
20812
20988
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
20813
20989
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
20814
- await access5(modulePath).catch(() => {
20990
+ await access6(modulePath).catch(() => {
20815
20991
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
20816
20992
  });
20817
- const loaded = await import(pathToFileURL(modulePath).href);
20993
+ const loaded = await import(pathToFileURL2(modulePath).href);
20818
20994
  const publisher = isRecord5(loaded) ? loaded.default ?? loaded.registry : undefined;
20819
20995
  if (!isPublisher(publisher)) {
20820
20996
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
@@ -20873,8 +21049,8 @@ var publishAbsoluteIosRelease = async (options) => {
20873
21049
  };
20874
21050
 
20875
21051
  // src/mobile/mobileInspect.ts
20876
- import { access as access6, readFile as readFile12 } from "fs/promises";
20877
- import { join as join10, relative as relative5, resolve as resolve6 } from "path";
21052
+ import { access as access7, readFile as readFile12 } from "fs/promises";
21053
+ import { join as join10, relative as relative6, resolve as resolve7 } from "path";
20878
21054
  var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1;
20879
21055
  var MOBILE_PACKAGE_NAMES = new Set([
20880
21056
  "@absolutejs/absolute",
@@ -20889,12 +21065,12 @@ var MOBILE_PACKAGE_NAMES = new Set([
20889
21065
  ]);
20890
21066
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20891
21067
  var portablePath2 = (projectRoot, path) => {
20892
- const value = relative5(resolve6(projectRoot), resolve6(path)).replaceAll("\\", "/");
21068
+ const value = relative6(resolve7(projectRoot), resolve7(path)).replaceAll("\\", "/");
20893
21069
  return value || ".";
20894
21070
  };
20895
21071
  var pathExists4 = async (path) => {
20896
21072
  try {
20897
- await access6(path);
21073
+ await access7(path);
20898
21074
  return true;
20899
21075
  } catch {
20900
21076
  return false;
@@ -21035,8 +21211,8 @@ var renderAbsoluteMobileProjectInspection = (report) => {
21035
21211
 
21036
21212
  // src/mobile/ciWorkflow.ts
21037
21213
  import { existsSync } from "fs";
21038
- import { access as access7, mkdir as mkdir7, readFile as readFile13, writeFile as writeFile10 } from "fs/promises";
21039
- import { dirname as dirname6, extname as extname2, relative as relative6, resolve as resolve7, sep as sep3 } from "path";
21214
+ import { access as access8, mkdir as mkdir8, readFile as readFile13, writeFile as writeFile10 } from "fs/promises";
21215
+ import { dirname as dirname7, extname as extname2, relative as relative7, resolve as resolve8, sep as sep4 } from "path";
21040
21216
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
21041
21217
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
21042
21218
  var CI_ENV_INDENTATION = 6;
@@ -21057,18 +21233,18 @@ var RESERVED_SECRET_NAMES = new Set([
21057
21233
  ]);
21058
21234
  var exists2 = async (path) => {
21059
21235
  try {
21060
- await access7(path);
21236
+ await access8(path);
21061
21237
  return true;
21062
21238
  } catch {
21063
21239
  return false;
21064
21240
  }
21065
21241
  };
21066
21242
  var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
21067
- var projectPath = (projectRoot, value, field, options = {}) => {
21068
- const root = resolve7(projectRoot);
21069
- const path = resolve7(root, value);
21070
- const portable = relative6(root, path).replaceAll("\\", "/");
21071
- if (portable === ".." || portable.startsWith(`..${sep3}`) || portable.startsWith("../") || portable === "") {
21243
+ var projectPath2 = (projectRoot, value, field, options = {}) => {
21244
+ const root = resolve8(projectRoot);
21245
+ const path = resolve8(root, value);
21246
+ const portable = relative7(root, path).replaceAll("\\", "/");
21247
+ if (portable === ".." || portable.startsWith(`..${sep4}`) || portable.startsWith("../") || portable === "") {
21072
21248
  throw new TypeError(`${field} must remain inside the project root.`);
21073
21249
  }
21074
21250
  if (/\r|\n/u.test(portable) || portable.startsWith("-"))
@@ -21078,11 +21254,11 @@ var projectPath = (projectRoot, value, field, options = {}) => {
21078
21254
  return portable;
21079
21255
  };
21080
21256
  var workflowOutputPath = (projectRoot, value) => {
21081
- const root = resolve7(projectRoot);
21082
- const workflows = resolve7(root, ".github/workflows");
21083
- const path = resolve7(root, value ?? ".github/workflows/absolute-mobile.yml");
21084
- const portable = relative6(workflows, path);
21085
- if (portable === ".." || portable.startsWith(`..${sep3}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
21257
+ const root = resolve8(projectRoot);
21258
+ const workflows = resolve8(root, ".github/workflows");
21259
+ const path = resolve8(root, value ?? ".github/workflows/absolute-mobile.yml");
21260
+ const portable = relative7(workflows, path);
21261
+ if (portable === ".." || portable.startsWith(`..${sep4}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
21086
21262
  throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
21087
21263
  }
21088
21264
  return path;
@@ -21449,9 +21625,9 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
21449
21625
  ].sort();
21450
21626
  const includePublishing = options.includePublishing === true;
21451
21627
  const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
21452
- const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
21453
- const configPath = options.configPath ? projectPath(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
21454
- const registryModule = projectPath(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
21628
+ const serverEntry = projectPath2(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
21629
+ const configPath = options.configPath ? projectPath2(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
21630
+ const registryModule = projectPath2(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
21455
21631
  const environment = commandEnvironment({
21456
21632
  configPath,
21457
21633
  registryModule,
@@ -21513,9 +21689,9 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
21513
21689
  const generated = createAbsoluteMobileGithubWorkflow(options);
21514
21690
  const previous = await exists2(path) ? await readFile13(path, "utf8") : undefined;
21515
21691
  if (previous !== undefined && previous !== generated.workflow && !options.force)
21516
- throw new TypeError(`${relative6(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
21692
+ throw new TypeError(`${relative7(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
21517
21693
  if (previous !== generated.workflow) {
21518
- await mkdir7(dirname6(path), { recursive: true });
21694
+ await mkdir8(dirname7(path), { recursive: true });
21519
21695
  await writeFile10(path, generated.workflow);
21520
21696
  }
21521
21697
  return {
@@ -21532,7 +21708,7 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
21532
21708
  import { createHash as createHash3, sign, verify } from "crypto";
21533
21709
  import {
21534
21710
  cp,
21535
- mkdir as mkdir8,
21711
+ mkdir as mkdir9,
21536
21712
  mkdtemp as mkdtemp2,
21537
21713
  readdir as readdir4,
21538
21714
  readFile as readFile14,
@@ -21541,7 +21717,7 @@ import {
21541
21717
  stat as stat4,
21542
21718
  writeFile as writeFile11
21543
21719
  } from "fs/promises";
21544
- import { dirname as dirname7, join as join11, relative as relative7, resolve as resolve8 } from "path";
21720
+ import { dirname as dirname8, join as join11, relative as relative8, resolve as resolve9 } from "path";
21545
21721
  var UPDATE_MANIFEST_FILE = "update.json";
21546
21722
  var UPDATE_FILES_DIRECTORY = "files";
21547
21723
  var sha256 = (value) => createHash3("sha256").update(value).digest("hex");
@@ -21553,7 +21729,7 @@ var listFiles = async (root, directory = root) => {
21553
21729
  return listFiles(root, path);
21554
21730
  if (!entry.isFile())
21555
21731
  throw new TypeError("Mobile updates cannot contain links or special files.");
21556
- return [relative7(root, path).replaceAll("\\", "/")];
21732
+ return [relative8(root, path).replaceAll("\\", "/")];
21557
21733
  }));
21558
21734
  return paths.flat().sort((left, right) => left.localeCompare(right));
21559
21735
  };
@@ -21567,8 +21743,8 @@ var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) =>
21567
21743
  }));
21568
21744
  var releaseIdFor = (value) => `amu_${sha256(canonicalizeAbsoluteMobileUpdate(value))}`;
21569
21745
  var buildAbsoluteMobileUpdate = async (options) => {
21570
- const bundleDirectory = resolve8(options.bundleDirectory);
21571
- const outputRoot = resolve8(options.outputDirectory);
21746
+ const bundleDirectory = resolve9(options.bundleDirectory);
21747
+ const outputRoot = resolve9(options.outputDirectory);
21572
21748
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
21573
21749
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
21574
21750
  const paths = await listFiles(bundleDirectory);
@@ -21596,7 +21772,7 @@ var buildAbsoluteMobileUpdate = async (options) => {
21596
21772
  value: signature.toString("base64")
21597
21773
  }
21598
21774
  });
21599
- await mkdir8(outputRoot, { recursive: true });
21775
+ await mkdir9(outputRoot, { recursive: true });
21600
21776
  const outputDirectory = join11(outputRoot, manifest.releaseId);
21601
21777
  const staging = await mkdtemp2(join11(outputRoot, ".stage-"));
21602
21778
  try {
@@ -21619,7 +21795,7 @@ var buildAbsoluteMobileUpdate = async (options) => {
21619
21795
  outputDirectory
21620
21796
  };
21621
21797
  };
21622
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile14(join11(resolve8(directory), UPDATE_MANIFEST_FILE), "utf8")));
21798
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile14(join11(resolve9(directory), UPDATE_MANIFEST_FILE), "utf8")));
21623
21799
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
21624
21800
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
21625
21801
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -21629,11 +21805,11 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
21629
21805
  };
21630
21806
 
21631
21807
  // src/mobile/expoUpdate.ts
21632
- import { access as access8, mkdir as mkdir9, readFile as readFile15, writeFile as writeFile12 } from "fs/promises";
21633
- import { dirname as dirname8, extname as extname3, join as join12 } from "path";
21808
+ import { access as access9, mkdir as mkdir10, readFile as readFile15, writeFile as writeFile12 } from "fs/promises";
21809
+ import { dirname as dirname9, extname as extname3, join as join12 } from "path";
21634
21810
  var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
21635
21811
  var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
21636
- var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21812
+ var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21637
21813
  var safePath = (value, field) => {
21638
21814
  if (typeof value !== "string" || value.length === 0)
21639
21815
  throw new TypeError(`Expo update ${field} is invalid.`);
@@ -21652,7 +21828,7 @@ var extension = (value, path) => {
21652
21828
  return value;
21653
21829
  };
21654
21830
  var asset = (value) => {
21655
- if (!object(value))
21831
+ if (!object2(value))
21656
21832
  throw new TypeError("Expo update asset is invalid.");
21657
21833
  const path = safePath(value.path, "asset path");
21658
21834
  const normalizedExtension = extension(value.extension ?? value.ext, path);
@@ -21662,9 +21838,9 @@ var asset = (value) => {
21662
21838
  };
21663
21839
  };
21664
21840
  var platform = (value) => {
21665
- if (!object(value) || !Array.isArray(value.assets))
21841
+ if (!object2(value) || !Array.isArray(value.assets))
21666
21842
  throw new TypeError("Expo update platform metadata is invalid.");
21667
- const launchValue = object(value.launchAsset) ? value.launchAsset : { path: value.bundle };
21843
+ const launchValue = object2(value.launchAsset) ? value.launchAsset : { path: value.bundle };
21668
21844
  const launchPath = safePath(launchValue.path, "launch bundle");
21669
21845
  const launchExtension = extension(launchValue.extension ?? null, launchPath);
21670
21846
  const assets = value.assets.map(asset);
@@ -21677,7 +21853,7 @@ var platform = (value) => {
21677
21853
  };
21678
21854
  };
21679
21855
  var parseAbsoluteExpoUpdateDescriptor = (value) => {
21680
- if (!object(value) || value.engine !== "expo" || value.format !== ABSOLUTE_EXPO_UPDATE_FORMAT || !object(value.expoConfig) || !object(value.platforms) || typeof value.runtimeVersion !== "string" || !/^[a-f0-9]{64}$/u.test(value.runtimeVersion))
21856
+ if (!object2(value) || value.engine !== "expo" || value.format !== ABSOLUTE_EXPO_UPDATE_FORMAT || !object2(value.expoConfig) || !object2(value.platforms) || typeof value.runtimeVersion !== "string" || !/^[a-f0-9]{64}$/u.test(value.runtimeVersion))
21681
21857
  throw new TypeError("AbsoluteJS Expo update descriptor is invalid.");
21682
21858
  const platforms = {};
21683
21859
  for (const name of ["android", "ios"]) {
@@ -21697,7 +21873,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
21697
21873
  };
21698
21874
  var finalizeAbsoluteExpoUpdateExport = async (options) => {
21699
21875
  const metadata = JSON.parse(await readFile15(join12(options.exportDirectory, "metadata.json"), "utf8"));
21700
- if (!object(metadata) || !object(metadata.fileMetadata))
21876
+ if (!object2(metadata) || !object2(metadata.fileMetadata))
21701
21877
  throw new TypeError("Expo export metadata.json is invalid.");
21702
21878
  const descriptor = parseAbsoluteExpoUpdateDescriptor({
21703
21879
  engine: "expo",
@@ -21707,11 +21883,11 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
21707
21883
  runtimeVersion: options.runtimeVersion
21708
21884
  });
21709
21885
  const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
21710
- await Promise.all(referenced.map(({ path }) => access8(join12(options.exportDirectory, path)).catch(() => {
21886
+ await Promise.all(referenced.map(({ path }) => access9(join12(options.exportDirectory, path)).catch(() => {
21711
21887
  throw new TypeError(`Expo export metadata references missing asset ${path}.`);
21712
21888
  })));
21713
21889
  const destination = join12(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
21714
- await mkdir9(dirname8(destination), { recursive: true });
21890
+ await mkdir10(dirname9(destination), { recursive: true });
21715
21891
  await writeFile12(destination, `${JSON.stringify(descriptor, null, "\t")}
21716
21892
  `);
21717
21893
  return { descriptor, path: destination };
@@ -21719,18 +21895,18 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
21719
21895
 
21720
21896
  // src/mobile/expoCodeSigning.ts
21721
21897
  var import_code_signing_certificates = __toESM(require_main(), 1);
21722
- import { access as access9, mkdir as mkdir10, writeFile as writeFile13 } from "fs/promises";
21723
- import { dirname as dirname9, isAbsolute as isAbsolute3, relative as relative8, resolve as resolve9, sep as sep4 } from "path";
21724
- var exists3 = async (path) => access9(path).then(() => true).catch(() => false);
21898
+ import { access as access10, mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
21899
+ import { dirname as dirname10, isAbsolute as isAbsolute4, relative as relative9, resolve as resolve10, sep as sep5 } from "path";
21900
+ var exists3 = async (path) => access10(path).then(() => true).catch(() => false);
21725
21901
  var inside = (root, path) => {
21726
- const location = relative8(root, path);
21727
- return location === "" || location !== ".." && !location.startsWith(`..${sep4}`) && !isAbsolute3(location);
21902
+ const location = relative9(root, path);
21903
+ return location === "" || location !== ".." && !location.startsWith(`..${sep5}`) && !isAbsolute4(location);
21728
21904
  };
21729
21905
  var generateAbsoluteExpoCodeSigning = async (options) => {
21730
- const root = resolve9(options.projectRoot);
21731
- const certificatePath = resolve9(root, options.certificatePath);
21732
- const privateKeyPath = resolve9(root, options.privateKeyPath);
21733
- const publicKeyPath = resolve9(root, options.publicKeyPath ?? resolve9(dirname9(privateKeyPath), "public-key.pem"));
21906
+ const root = resolve10(options.projectRoot);
21907
+ const certificatePath = resolve10(root, options.certificatePath);
21908
+ const privateKeyPath = resolve10(root, options.privateKeyPath);
21909
+ const publicKeyPath = resolve10(root, options.publicKeyPath ?? resolve10(dirname10(privateKeyPath), "public-key.pem"));
21734
21910
  if (!inside(root, certificatePath))
21735
21911
  throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
21736
21912
  if (inside(root, privateKeyPath) || inside(root, publicKeyPath))
@@ -21760,7 +21936,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
21760
21936
  import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
21761
21937
  const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
21762
21938
  const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
21763
- await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir10(dirname9(path), { recursive: true })));
21939
+ await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir11(dirname10(path), { recursive: true })));
21764
21940
  await Promise.all([
21765
21941
  writeFile13(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
21766
21942
  writeFile13(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
@@ -21775,26 +21951,26 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
21775
21951
  };
21776
21952
 
21777
21953
  // src/mobile/updatePublisher.ts
21778
- import { access as access10 } from "fs/promises";
21779
- import { isAbsolute as isAbsolute4, relative as relative9, resolve as resolve10, sep as sep5 } from "path";
21780
- import { pathToFileURL as pathToFileURL2 } from "url";
21781
- var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21782
- var isPublisher2 = (value) => object2(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
21783
- var projectPath2 = (projectRoot, requested, label) => {
21784
- const root = resolve10(projectRoot);
21785
- const path = resolve10(root, requested);
21786
- const projectRelative = relative9(root, path);
21787
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep5}`) || isAbsolute4(projectRelative))
21954
+ import { access as access11 } from "fs/promises";
21955
+ import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep6 } from "path";
21956
+ import { pathToFileURL as pathToFileURL3 } from "url";
21957
+ var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21958
+ var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
21959
+ var projectPath3 = (projectRoot, requested, label) => {
21960
+ const root = resolve11(projectRoot);
21961
+ const path = resolve11(root, requested);
21962
+ const projectRelative = relative10(root, path);
21963
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute5(projectRelative))
21788
21964
  throw new TypeError(`${label} must remain inside the project.`);
21789
21965
  return path;
21790
21966
  };
21791
21967
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
21792
- const modulePath = projectPath2(projectRoot, requestedModulePath, "mobile update registry");
21793
- await access10(modulePath).catch(() => {
21968
+ const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
21969
+ await access11(modulePath).catch(() => {
21794
21970
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
21795
21971
  });
21796
- const loaded = await import(pathToFileURL2(modulePath).href);
21797
- const publisher = object2(loaded) ? loaded.default ?? loaded.registry : undefined;
21972
+ const loaded = await import(pathToFileURL3(modulePath).href);
21973
+ const publisher = object3(loaded) ? loaded.default ?? loaded.registry : undefined;
21798
21974
  if (!isPublisher2(publisher))
21799
21975
  throw new TypeError("Mobile update registry must implement publishUpdate, promoteUpdate, and rollbackUpdate.");
21800
21976
  return publisher;
@@ -21812,7 +21988,7 @@ var promoteAbsoluteMobileUpdate = async (options) => {
21812
21988
  return result;
21813
21989
  };
21814
21990
  var publishAbsoluteMobileUpdate = async (options) => {
21815
- const releaseDirectory = projectPath2(options.projectRoot, options.releaseDirectory, "mobile update release directory");
21991
+ const releaseDirectory = projectPath3(options.projectRoot, options.releaseDirectory, "mobile update release directory");
21816
21992
  const manifest = await readAbsoluteMobileUpdate(releaseDirectory);
21817
21993
  const result = await options.publisher.publishUpdate({
21818
21994
  manifest,
@@ -21948,7 +22124,7 @@ var requireMobileConfig = (value) => {
21948
22124
  var capacitorExecutable = async (projectRoot) => {
21949
22125
  const executable = join13(projectRoot, "node_modules", ".bin", "cap");
21950
22126
  try {
21951
- await access11(executable);
22127
+ await access12(executable);
21952
22128
  return executable;
21953
22129
  } catch {
21954
22130
  throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
@@ -21971,7 +22147,7 @@ var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.re
21971
22147
  var expoExecutable = async (project) => {
21972
22148
  const executable = join13(project, "node_modules", ".bin", "expo");
21973
22149
  try {
21974
- await access11(executable);
22150
+ await access12(executable);
21975
22151
  return executable;
21976
22152
  } catch {
21977
22153
  throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
@@ -22206,7 +22382,7 @@ var sync = async (args) => {
22206
22382
  };
22207
22383
  var associations = async (args) => {
22208
22384
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
22209
- const outputDirectory = resolve11(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
22385
+ const outputDirectory = resolve12(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
22210
22386
  if (args.includes("--verify")) {
22211
22387
  const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
22212
22388
  console.log(`Verified ${result2.results.length} hosted association files`);
@@ -22258,7 +22434,7 @@ var generateGithubCi = async (args) => {
22258
22434
  const publicResult = {
22259
22435
  changed: result.changed,
22260
22436
  format: result.format,
22261
- path: relative10(projectRoot, result.path).replaceAll("\\", "/"),
22437
+ path: relative11(projectRoot, result.path).replaceAll("\\", "/"),
22262
22438
  platforms: result.platforms,
22263
22439
  publishing: result.publishing,
22264
22440
  requiredSecrets: result.requiredSecrets
@@ -22370,7 +22546,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
22370
22546
  };
22371
22547
  await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
22372
22548
  const temporaryParent = join13(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
22373
- await mkdir11(temporaryParent, { recursive: true });
22549
+ await mkdir12(temporaryParent, { recursive: true });
22374
22550
  const temporaryDirectory = await mkdtemp3(join13(temporaryParent, ".stage-"));
22375
22551
  try {
22376
22552
  await runExpo(mobile.nativeProjectDirectory, [
@@ -22430,9 +22606,9 @@ var buildMobileUpdate = async (args) => {
22430
22606
  const runtimeFingerprint = isRecord6(embedded) ? embedded.nativeRuntime : undefined;
22431
22607
  if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
22432
22608
  throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
22433
- const privateKey = await readFile16(resolve11(projectRoot, signingKeyPath));
22609
+ const privateKey = await readFile16(resolve12(projectRoot, signingKeyPath));
22434
22610
  const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
22435
- const derivedPublicKey = createPublicKey(privateKey).export({
22611
+ const derivedPublicKey = createPublicKey2(privateKey).export({
22436
22612
  format: "der",
22437
22613
  type: "spki"
22438
22614
  });
@@ -22493,7 +22669,7 @@ var generateExpoUpdateSigning = async (args) => {
22493
22669
  ...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
22494
22670
  ...validityYears === undefined ? {} : { validityYears }
22495
22671
  });
22496
- const certificatePath = relative10(projectRoot, result.certificatePath).replaceAll("\\", "/");
22672
+ const certificatePath = relative11(projectRoot, result.certificatePath).replaceAll("\\", "/");
22497
22673
  sendTelemetryEvent("mobile:update-code-signing-generated", {
22498
22674
  engine: "expo",
22499
22675
  validityYears: validityYears ?? 10
@@ -22516,13 +22692,56 @@ var updateRollout = (args, fallback) => {
22516
22692
  return rollout;
22517
22693
  };
22518
22694
  var mobileUpdatePublisher = async (args) => {
22519
- const { projectRoot } = await loadMobile(valueAfter(args, "--config"));
22520
- const modulePath = valueAfter(args, "--registry") ?? "mobile.release.ts";
22695
+ const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
22696
+ if (!mobile.updates || !mobile.updateServer)
22697
+ throw new TypeError("Mobile updates are not configured.");
22698
+ const modulePath = valueAfter(args, "--registry") ?? mobile.updateServer.registryModule;
22521
22699
  return {
22522
22700
  projectRoot,
22523
22701
  publisher: await loadAbsoluteMobileUpdatePublisher(projectRoot, modulePath)
22524
22702
  };
22525
22703
  };
22704
+ var provisionMobileUpdate = async (args) => {
22705
+ const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
22706
+ if (!mobile.updates || !mobile.updateServer)
22707
+ throw new TypeError("mobile update provision requires mobile.updates.publicKeys in absolute.config.ts.");
22708
+ const requestedStorage = valueAfter(args, "--storage") ?? "local";
22709
+ if (requestedStorage !== "local" && requestedStorage !== "s3")
22710
+ throw new TypeError("--storage must be local or s3.");
22711
+ const modulePath = valueAfter(args, "--registry") ?? mobile.updateServer.registryModule;
22712
+ const packages = [
22713
+ "@absolutejs/deploy@0.25.5",
22714
+ "@absolutejs/blob@0.5.2",
22715
+ ...requestedStorage === "s3" ? [
22716
+ "@aws-sdk/client-s3@3.1095.0",
22717
+ "@aws-sdk/lib-storage@3.1095.0",
22718
+ "@aws-sdk/s3-request-presigner@3.1095.0"
22719
+ ] : []
22720
+ ];
22721
+ const installed = await directProjectPackages(projectRoot);
22722
+ const missing = packages.filter((spec) => !installed.has(packageNameFromSpec(spec)));
22723
+ await installApprovedPackages(projectRoot, args, `Mobile update serving needs ${missing.map(packageNameFromSpec).join(", ")}. Install them now?`, missing);
22724
+ const path = await writeAbsoluteMobileUpdateRegistry({
22725
+ force: args.includes("--force"),
22726
+ modulePath,
22727
+ projectRoot,
22728
+ publicKeys: mobile.updates.publicKeys,
22729
+ storage: requestedStorage
22730
+ });
22731
+ console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative11(projectRoot, path).replaceAll("\\", "/")}.`);
22732
+ if (requestedStorage === "local")
22733
+ console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
22734
+ else
22735
+ console.log("Set ABSOLUTE_MOBILE_UPDATE_S3_BUCKET and standard AWS credentials on the trusted server; endpoint and region overrides are optional.");
22736
+ const expoSigning = mobile.updates.expoCodeSigning;
22737
+ if (expoSigning) {
22738
+ const signingKey = mobile.updateServer.expoCodeSigningKeys[expoSigning.keyId];
22739
+ if (!signingKey)
22740
+ throw new TypeError("The active Expo server signing key is missing.");
22741
+ console.log(`Set ${signingKey.privateKeyEnv} only on the trusted server.`);
22742
+ }
22743
+ return path;
22744
+ };
22526
22745
  var publishMobileUpdate = async (args) => {
22527
22746
  const releaseDirectory = args.find((value, index) => {
22528
22747
  if (value.startsWith("-"))
@@ -23144,7 +23363,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
23144
23363
  }
23145
23364
  return { https: args.includes("--https"), port };
23146
23365
  }
23147
- const instances = listLiveInstances().filter((instance2) => resolve11(instance2.cwd) === resolve11(projectRoot) && instance2.source === "dev" && instance2.port !== null);
23366
+ const instances = listLiveInstances().filter((instance2) => resolve12(instance2.cwd) === resolve12(projectRoot) && instance2.source === "dev" && instance2.port !== null);
23148
23367
  if (instances.length !== 1) {
23149
23368
  throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for Android to report ready, then run `absolute mobile test android`." : "Multiple dev servers are running for this project. Select one with mobile test android --port <port>.");
23150
23369
  }
@@ -23191,8 +23410,8 @@ var selectAndroidSerial = (adb, explicitSerial) => {
23191
23410
  return selected;
23192
23411
  };
23193
23412
  var safeArtifactRoot = (projectRoot, value) => {
23194
- const root = resolve11(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
23195
- if (root !== projectRoot && !root.startsWith(`${resolve11(projectRoot)}/`)) {
23413
+ const root = resolve12(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
23414
+ if (root !== projectRoot && !root.startsWith(`${resolve12(projectRoot)}/`)) {
23196
23415
  throw new TypeError("mobile test --artifacts must remain inside the project.");
23197
23416
  }
23198
23417
  return root;
@@ -23220,7 +23439,7 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
23220
23439
  });
23221
23440
  };
23222
23441
  var writeAndroidFailureArtifacts = async (options) => {
23223
- await mkdir11(options.artifactRoot, { recursive: true });
23442
+ await mkdir12(options.artifactRoot, { recursive: true });
23224
23443
  const screenshot = options.session ? await options.session.screenshot(join13(options.artifactRoot, "android-failure.png")).catch(() => {
23225
23444
  return;
23226
23445
  }) : undefined;
@@ -23359,14 +23578,14 @@ var requireIosTestContext = (args, projectRoot) => {
23359
23578
  const port = Number(explicit);
23360
23579
  if (!Number.isInteger(port) || port < 1 || port > 65535)
23361
23580
  throw new TypeError("mobile test --port must be a valid TCP port.");
23362
- const instance2 = listLiveInstances().find((candidate) => resolve11(candidate.cwd) === resolve11(projectRoot) && candidate.source === "dev" && candidate.port === port);
23581
+ const instance2 = listLiveInstances().find((candidate) => resolve12(candidate.cwd) === resolve12(projectRoot) && candidate.source === "dev" && candidate.port === port);
23363
23582
  return {
23364
23583
  https: instance2?.https ?? args.includes("--https"),
23365
23584
  instance: instance2,
23366
23585
  port
23367
23586
  };
23368
23587
  }
23369
- const instances = listLiveInstances().filter((instance2) => resolve11(instance2.cwd) === resolve11(projectRoot) && instance2.source === "dev" && instance2.port !== null);
23588
+ const instances = listLiveInstances().filter((instance2) => resolve12(instance2.cwd) === resolve12(projectRoot) && instance2.source === "dev" && instance2.port !== null);
23370
23589
  if (instances.length !== 1)
23371
23590
  throw new TypeError(instances.length === 0 ? "No running AbsoluteJS dev server was found for this project. Start `bun dev`, wait for iOS to report ready, then run `absolute mobile test ios`." : "Multiple dev servers are running for this project. Select one with mobile test ios --port <port>.");
23372
23591
  const [instance] = instances;
@@ -23489,7 +23708,7 @@ var requireCapturedCommand = (command, label) => {
23489
23708
  return result;
23490
23709
  };
23491
23710
  var writeIosFailureArtifacts = async (options) => {
23492
- await mkdir11(options.artifactRoot, { recursive: true });
23711
+ await mkdir12(options.artifactRoot, { recursive: true });
23493
23712
  const screenshot = join13(options.artifactRoot, "ios-failure.png");
23494
23713
  const screenshotResult = captureCommand2([
23495
23714
  options.xcrun,
@@ -23527,8 +23746,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
23527
23746
  var absolutejsVersionForReport = async () => {
23528
23747
  let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
23529
23748
  const versions = await Promise.all([
23530
- resolve11(import.meta.dir, "..", "..", "package.json"),
23531
- resolve11(import.meta.dir, "..", "..", "..", "package.json")
23749
+ resolve12(import.meta.dir, "..", "..", "package.json"),
23750
+ resolve12(import.meta.dir, "..", "..", "..", "package.json")
23532
23751
  ].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
23533
23752
  for (const version of versions) {
23534
23753
  if (version === "unknown")
@@ -23752,7 +23971,7 @@ var testIos = async (args) => {
23752
23971
  mobile.appId
23753
23972
  ], "iOS app launch");
23754
23973
  await waitForIosHmrClient({ https, port, timeoutMs });
23755
- await mkdir11(artifactRoot, { recursive: true });
23974
+ await mkdir12(artifactRoot, { recursive: true });
23756
23975
  const screenshot = join13(artifactRoot, "ios-simulator.png");
23757
23976
  requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
23758
23977
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
@@ -23895,6 +24114,10 @@ var runMobile = async (args) => {
23895
24114
  await buildMobileUpdate(args.slice(2));
23896
24115
  return;
23897
24116
  }
24117
+ if (command === "update" && args[1] === "provision") {
24118
+ await provisionMobileUpdate(args.slice(2));
24119
+ return;
24120
+ }
23898
24121
  if (command === "update" && args[1] === "signing" && args[2] === "generate") {
23899
24122
  await generateExpoUpdateSigning(args.slice(3));
23900
24123
  return;
@@ -23919,7 +24142,7 @@ var runMobile = async (args) => {
23919
24142
  await publishIos(args.slice(2));
23920
24143
  return;
23921
24144
  }
23922
- throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--outdir dir] [--web-outdir dir] [--unsigned] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
24145
+ throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
23923
24146
  };
23924
24147
  export {
23925
24148
  runMobile