@absolutejs/absolute 0.20.0-beta.48 → 0.20.0-beta.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -284,7 +284,9 @@ The generated workflow keeps signing and provider credentials behind the
284
284
  An experimental Expo Router hybrid is also available behind
285
285
  `mobile.engine: 'expo'`. Explicit React Native routes use native UI while every
286
286
  unclaimed AbsoluteJS route remains an embedded, signed web route regardless of
287
- framework. Capacitor is still the default and production-ready path. See the
287
+ framework. Capacitor is still the default; Expo Android now supports audited,
288
+ signed production AABs, generated CI, and Google Play publishing while Expo iOS
289
+ release automation remains experimental. See the
288
290
  [Expo hybrid experiment](docs/MOBILE_EXPO_EXPERIMENT.md).
289
291
  For Expo development, `bun dev` now owns the Bun server, Metro development
290
292
  client, configured emulator/simulator builds, categorized logs, and both native
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // .angular-partial-tmp-ntJYlQ/src/core/streamingSlotRegistrar.ts
4
+ // .angular-partial-tmp-gW9hwe/src/core/streamingSlotRegistrar.ts
5
5
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
6
6
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
7
7
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  var __require = import.meta.require;
3
3
 
4
- // .angular-partial-tmp-ntJYlQ/src/core/streamingSlotRegistrar.ts
4
+ // .angular-partial-tmp-gW9hwe/src/core/streamingSlotRegistrar.ts
5
5
  var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
6
6
  var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
7
7
  var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
@@ -48,7 +48,7 @@ var warnMissingStreamingSlotCollector = (primitiveName) => {
48
48
  getWarningController()?.maybeWarn(primitiveName);
49
49
  };
50
50
 
51
- // .angular-partial-tmp-ntJYlQ/src/core/streamingSlotRegistry.ts
51
+ // .angular-partial-tmp-gW9hwe/src/core/streamingSlotRegistry.ts
52
52
  var STREAMING_SLOT_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotAsyncLocalStorage");
53
53
  var isObjectRecord2 = (value) => Boolean(value) && typeof value === "object";
54
54
  var isAsyncLocalStorage = (value) => isObjectRecord2(value) && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
package/dist/cli/index.js CHANGED
@@ -19717,7 +19717,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19717
19717
  path,
19718
19718
  remediation,
19719
19719
  status: "warn"
19720
- }), readJsonObject = async (path) => {
19720
+ }), isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJsonObject = async (path) => {
19721
19721
  const value = JSON.parse(await readFile18(path, "utf8"));
19722
19722
  if (typeof value !== "object" || value === null || Array.isArray(value))
19723
19723
  throw new TypeError("JSON root must be an object.");
@@ -19759,6 +19759,37 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19759
19759
  } catch (error) {
19760
19760
  return fail5("mobile.capacitor-versions", error instanceof Error ? error.message : "Capacitor package versions could not be validated.", manifestPath, "Pin @capacitor/core, @capacitor/cli, and each configured platform to exact versions on the same major/minor line, then reinstall.");
19761
19761
  }
19762
+ }, satisfiesGeneratedVersion = (declared, installed) => {
19763
+ if (EXACT_VERSION_PATTERN.test(declared))
19764
+ return declared === installed;
19765
+ if (!declared.startsWith("~"))
19766
+ return false;
19767
+ const expected = declared.slice(1).split(".").map(Number);
19768
+ const actual = installed.split(".").map(Number);
19769
+ return actual[0] === expected[0] && actual[1] === expected[1] && (actual[2] ?? NOT_FOUND3) >= (expected[2] ?? 0);
19770
+ }, expoVersionCheck = async (config) => {
19771
+ const manifestPath = join52(config.nativeProjectDirectory, "package.json");
19772
+ try {
19773
+ const manifest = await readJsonObject(manifestPath);
19774
+ const declarations = packageDeclarations(manifest);
19775
+ const required = ["expo", "expo-router", "react", "react-native"];
19776
+ const missingRequired = required.find((name) => !declarations.has(name));
19777
+ if (missingRequired)
19778
+ throw new TypeError(`Generated Expo project is missing ${missingRequired}.`);
19779
+ const installedVersions = await Promise.all([...declarations].map(async ([name, declared]) => ({
19780
+ declared,
19781
+ installed: await readJsonObject(join52(config.nativeProjectDirectory, "node_modules", name, "package.json")),
19782
+ name
19783
+ })));
19784
+ const mismatch = installedVersions.find(({ declared, installed }) => typeof installed.version !== "string" || !satisfiesGeneratedVersion(declared, installed.version));
19785
+ if (mismatch)
19786
+ throw new TypeError(`Generated Expo dependency ${mismatch.name}@${mismatch.declared} does not match its installed version.`);
19787
+ if (!await pathExists6(join52(config.nativeProjectDirectory, "bun.lock")))
19788
+ throw new TypeError("Generated Expo dependency lockfile is missing.");
19789
+ return pass("mobile.expo-versions", `Generated Expo SDK dependencies are pinned, installed, and locked (${declarations.get("expo")}).`, manifestPath);
19790
+ } catch (error) {
19791
+ return fail5("mobile.expo-versions", error instanceof Error ? error.message : "Generated Expo dependency versions could not be validated.", manifestPath, "Run `absolute mobile init --yes`, then rebuild the production Expo project.");
19792
+ }
19762
19793
  }, dependencyLockCheck = async (projectRoot) => {
19763
19794
  const present = (await Promise.all(LOCK_FILES.map(async (name) => ({
19764
19795
  exists: await pathExists6(join52(projectRoot, name)),
@@ -19850,6 +19881,63 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19850
19881
  } catch (error) {
19851
19882
  return fail5(`${platform6}.content-security-policy`, error instanceof Error ? error.message : "Packaged shell CSP could not be validated.", path, "Rebuild the production mobile bundle with the AbsoluteJS-generated shell.");
19852
19883
  }
19884
+ }, expoApplicationConfigCheck = async (config) => {
19885
+ const path = join52(config.nativeProjectDirectory, "app.json");
19886
+ try {
19887
+ const root = await readJsonObject(path);
19888
+ if (!isRecord14(root.expo))
19889
+ throw new TypeError("Generated Expo application config is invalid.");
19890
+ const { expo } = root;
19891
+ if (expo.name !== config.appName || !isRecord14(expo.android) || expo.android.package !== config.appId) {
19892
+ throw new TypeError("Generated Expo Android identity does not match mobile config.");
19893
+ }
19894
+ if (!isRecord14(expo.runtimeVersion) || expo.runtimeVersion.policy !== "appVersion") {
19895
+ throw new TypeError("Generated Expo runtimeVersion must follow the native app version.");
19896
+ }
19897
+ if (Array.isArray(expo.plugins) && expo.plugins.some((plugin) => typeof plugin === "string" && plugin.includes("withAbsoluteDevelopmentCa"))) {
19898
+ throw new TypeError("Generated Expo production config includes the development CA plugin.");
19899
+ }
19900
+ return pass("expo.app-config", "Expo application identity and runtime policy match the production mobile config.", path);
19901
+ } catch (error) {
19902
+ return fail5("expo.app-config", error instanceof Error ? error.message : "Generated Expo application config could not be validated.", path, "Run `absolute mobile build android`; do not edit the generated Expo project.");
19903
+ }
19904
+ }, expoEmbeddedAssetsCheck = async (config) => {
19905
+ const generated = join52(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
19906
+ const assetsRoot = join52(config.nativeProjectDirectory, "assets", "absolute");
19907
+ try {
19908
+ const [source, manifest] = await Promise.all([
19909
+ readFile18(generated, "utf8"),
19910
+ readJsonObject(join52(config.bundleDirectory, "absolute-mobile-manifest.json"))
19911
+ ]);
19912
+ if (source.includes("embedded AbsoluteJS bundle is unavailable") || typeof manifest.appBuild !== "string" || !source.includes(JSON.stringify(manifest.appBuild)) || !source.includes(JSON.stringify(config.productionOrigin))) {
19913
+ throw new TypeError("Generated Expo assets do not contain the prepared production release identity.");
19914
+ }
19915
+ const sourceFiles = (await Array.fromAsync(new Bun.Glob("**/*").scan({
19916
+ cwd: config.bundleDirectory,
19917
+ onlyFiles: true
19918
+ }))).sort();
19919
+ const embeddedFiles = (await Array.fromAsync(new Bun.Glob("*.absasset").scan({
19920
+ cwd: assetsRoot,
19921
+ onlyFiles: true
19922
+ }))).sort();
19923
+ if (sourceFiles.length === 0 || sourceFiles.length !== embeddedFiles.length)
19924
+ throw new TypeError("Generated Expo asset count does not match the prepared mobile bundle.");
19925
+ const matches = await Promise.all(sourceFiles.map(async (path, index) => {
19926
+ const embedded = embeddedFiles[index];
19927
+ if (!embedded)
19928
+ return false;
19929
+ const [left, right] = await Promise.all([
19930
+ readFile18(join52(config.bundleDirectory, path)),
19931
+ readFile18(join52(assetsRoot, embedded))
19932
+ ]);
19933
+ return left.equals(right);
19934
+ }));
19935
+ if (matches.some((value) => !value))
19936
+ throw new TypeError("Generated Expo asset bytes differ from the prepared mobile bundle.");
19937
+ return pass("expo.bundle-projection", `Expo embeds the complete signed mobile bundle as ${embeddedFiles.length} opaque asset(s).`, generated);
19938
+ } catch (error) {
19939
+ return fail5("expo.bundle-projection", error instanceof Error ? error.message : "Generated Expo assets could not be validated.", generated, "Run `absolute mobile build android` to regenerate and verify the production asset projection.");
19940
+ }
19853
19941
  }, sourceFiles = async (root, extensions) => {
19854
19942
  if (!await pathExists6(root))
19855
19943
  return [];
@@ -19882,13 +19970,8 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19882
19970
  }, androidDeepLinkProjectionCheck = async (config, manifestPath) => {
19883
19971
  try {
19884
19972
  const source = await readFile18(manifestPath, "utf8");
19885
- const required = [
19886
- 'android:autoVerify="true"',
19887
- "android.intent.category.BROWSABLE",
19888
- ...config.deepLinkHosts.map((host2) => `android:scheme="https" android:host="${host2}"`),
19889
- ...config.deepLinkScheme ? [`android:scheme="${config.deepLinkScheme}"`] : []
19890
- ];
19891
- if (required.some((value) => !source.includes(value)))
19973
+ const hasWebHost = (host2) => [...source.matchAll(/<data\b[^>]*>/giu)].some(([tag]) => tag.includes('android:scheme="https"') && tag.includes(`android:host="${host2}"`));
19974
+ if (!source.includes('android:autoVerify="true"') || !source.includes("android.intent.category.BROWSABLE") || config.deepLinkHosts.some((host2) => !hasWebHost(host2)) || config.deepLinkScheme && !source.includes(`android:scheme="${config.deepLinkScheme}"`))
19892
19975
  throw new TypeError("Android App Link or custom-scheme projection does not match mobile config.");
19893
19976
  return pass("android.deep-links", "Android verified links and custom scheme match the effective mobile config.", manifestPath);
19894
19977
  } catch (error) {
@@ -19997,8 +20080,21 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19997
20080
  }, deviceCapabilityReleaseCheck = async (config, projectRoot) => {
19998
20081
  const manifestPath = join52(projectRoot, "package.json");
19999
20082
  try {
20000
- const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot);
20001
- assertAbsoluteDeviceCapabilityPackages(projectRoot, plan);
20083
+ const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot, config.engine);
20084
+ const assertPackages = async () => {
20085
+ if (config.engine === "capacitor")
20086
+ return assertAbsoluteDeviceCapabilityPackages(projectRoot, plan);
20087
+ const generated = await readJsonObject(join52(config.nativeProjectDirectory, "package.json"));
20088
+ const declarations = packageDeclarations(generated);
20089
+ const missing = plan.requiredPackages.filter((spec) => {
20090
+ const separator = spec.lastIndexOf("@");
20091
+ return declarations.get(spec.slice(0, separator)) !== spec.slice(separator + 1);
20092
+ });
20093
+ if (missing.length > 0)
20094
+ throw new TypeError(`Generated Expo project is missing detected capability packages: ${missing.join(", ")}.`);
20095
+ return;
20096
+ };
20097
+ await assertPackages();
20002
20098
  const requirements = absoluteDeviceNativeRequirements(plan);
20003
20099
  const androidCheck = await androidDevicePermissionCheck(config, requirements.androidPermissions);
20004
20100
  if (androidCheck)
@@ -20035,6 +20131,26 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
20035
20131
  ...check2,
20036
20132
  path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
20037
20133
  }));
20134
+ }, inspectExpoAndroidRelease = async (config, projectRoot) => {
20135
+ const androidRoot = join52(config.nativeProjectDirectory, "android");
20136
+ const manifestPath = join52(androidRoot, "app", "src", "main", "AndroidManifest.xml");
20137
+ const journalPath = join52(projectRoot, ".absolutejs", "mobile", "expo-dev-session", "journal.json");
20138
+ const checks = await Promise.all([
20139
+ journalReleaseCheck(journalPath, "android"),
20140
+ expoApplicationConfigCheck(config),
20141
+ expoEmbeddedAssetsCheck(config),
20142
+ manifestReleaseCheck(manifestPath),
20143
+ hmrAssetsReleaseCheck(config.bundleDirectory),
20144
+ embeddedBundleReleaseCheck(config, projectRoot, "android", config.bundleDirectory),
20145
+ contentSecurityPolicyCheck(config, "android", config.bundleDirectory),
20146
+ androidNativeSecurityCheck(androidRoot),
20147
+ androidExportedComponentsCheck(manifestPath),
20148
+ androidDeepLinkProjectionCheck(config, manifestPath)
20149
+ ]);
20150
+ return checks.map((check2) => ({
20151
+ ...check2,
20152
+ path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
20153
+ }));
20038
20154
  }, inspectIosRelease = async (config, projectRoot) => {
20039
20155
  const iosAppRoot = join52(config.nativeProjectDirectory, "ios", "App", "App");
20040
20156
  const nativeConfigPath = join52(iosAppRoot, "capacitor.config.json");
@@ -20094,17 +20210,18 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
20094
20210
  Promise.resolve(productionOriginCheck(config, projectRoot)),
20095
20211
  Promise.resolve(associationIdentityCheck(config, projectRoot)),
20096
20212
  dependencyLockCheck(projectRoot),
20097
- capacitorVersionCheck(config, projectRoot)
20213
+ config.engine === "expo" ? expoVersionCheck(config) : capacitorVersionCheck(config, projectRoot)
20098
20214
  ]);
20099
20215
  const checks = globalChecks.map((check2) => ({
20100
20216
  ...check2,
20101
20217
  path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
20102
20218
  }));
20103
20219
  if (config.platforms.includes("android"))
20104
- checks.push(...await inspectAndroidRelease(config, projectRoot));
20105
- if (config.platforms.includes("ios")) {
20106
- checks.push(...await inspectIosRelease(config, projectRoot));
20107
- }
20220
+ checks.push(...config.engine === "expo" ? await inspectExpoAndroidRelease(config, projectRoot) : await inspectAndroidRelease(config, projectRoot));
20221
+ if (config.platforms.includes("ios"))
20222
+ checks.push(...config.engine === "expo" ? [
20223
+ fail5("expo.ios-release", "Expo iOS production release automation is not implemented yet.", config.nativeProjectDirectory, "Build Android independently or wait for the Expo iOS signing checkpoint.")
20224
+ ] : await inspectIosRelease(config, projectRoot));
20108
20225
  const syncSchema = syncSchemaReleaseCheck(projectRoot);
20109
20226
  if (syncSchema) {
20110
20227
  checks.push({
@@ -20161,14 +20278,15 @@ import {
20161
20278
  mkdir as mkdir13,
20162
20279
  mkdtemp as mkdtemp6,
20163
20280
  readFile as readFile19,
20281
+ realpath as realpath2,
20164
20282
  rename as rename13,
20165
20283
  rm as rm9,
20166
20284
  stat as stat3,
20167
20285
  writeFile as writeFile15
20168
20286
  } from "fs/promises";
20169
20287
  import { dirname as dirname31, isAbsolute as isAbsolute7, join as join53, relative as relative27, resolve as resolve40, sep as sep7 } from "path";
20170
- var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
20171
- if (!isRecord14(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
20288
+ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
20289
+ if (!isRecord15(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
20172
20290
  throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
20173
20291
  }
20174
20292
  return {
@@ -20230,7 +20348,18 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord14 = (value) => typeof value ==
20230
20348
  ]);
20231
20349
  if (result.exitCode !== 0)
20232
20350
  throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
20233
- }, sha256File2 = async (path) => createHash14("sha256").update(await readFile19(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
20351
+ }, sha256File2 = async (path) => createHash14("sha256").update(await readFile19(path)).digest("hex"), fingerprintExpoAndroidProject = async (nativeDirectory) => {
20352
+ const root = await realpath2(nativeDirectory);
20353
+ const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
20354
+ const records = await Promise.all(files.filter((path) => {
20355
+ const parts = path.replaceAll("\\", "/").split("/");
20356
+ return !parts.includes(".gradle") && !parts.includes("build");
20357
+ }).sort().map(async (path) => {
20358
+ const contents = await readFile19(join53(root, path));
20359
+ return `${path.replaceAll("\\", "/")}\x00${createHash14("sha256").update(contents).digest("hex")}\x00`;
20360
+ }));
20361
+ return createHash14("sha256").update(records.join("")).digest("hex");
20362
+ }, safeOutputDirectory2 = (projectRoot, requested) => {
20234
20363
  const root = resolve40(projectRoot);
20235
20364
  const output = resolve40(root, requested ?? ".absolutejs/mobile/releases/android");
20236
20365
  const projectRelative = relative27(root, output);
@@ -20271,7 +20400,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord14 = (value) => typeof value ==
20271
20400
  });
20272
20401
  }
20273
20402
  }, requireManifestIdentity = (value, expected) => {
20274
- if (!isRecord14(value)) {
20403
+ if (!isRecord15(value)) {
20275
20404
  throw new TypeError("Existing Android release metadata is invalid.");
20276
20405
  }
20277
20406
  const { artifact } = value;
@@ -20288,6 +20417,9 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord14 = (value) => typeof value ==
20288
20417
  }
20289
20418
  const projectRoot = resolve40(options.projectRoot);
20290
20419
  const host2 = options.host ?? detectAbsoluteMobileHost();
20420
+ if (options.config.engine === "expo" && host2 === "wsl") {
20421
+ 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.");
20422
+ }
20291
20423
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host2);
20292
20424
  const nativeDirectory = join53(options.config.nativeProjectDirectory, "android");
20293
20425
  const manifest = requireManifest2(JSON.parse(await readFile19(join53(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
@@ -20296,7 +20428,9 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord14 = (value) => typeof value ==
20296
20428
  }
20297
20429
  let { versionCode } = options;
20298
20430
  if (options.prepareVersionCode) {
20299
- const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
20431
+ const nativeFingerprint = options.config.engine === "expo" ? await fingerprintExpoAndroidProject(nativeDirectory) : await fingerprintAbsoluteAndroidNativeProject({
20432
+ nativeDirectory
20433
+ });
20300
20434
  const buildIdentity = createHash14("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
20301
20435
  versionCode = await options.prepareVersionCode(buildIdentity);
20302
20436
  }
@@ -20305,6 +20439,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord14 = (value) => typeof value ==
20305
20439
  }
20306
20440
  const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
20307
20441
  capture: options.capture,
20442
+ env: options.env,
20308
20443
  gradleArguments: versionCode === undefined ? [] : [`-Pandroid.injected.version.code=${versionCode}`],
20309
20444
  project: {
20310
20445
  androidRoot,
@@ -20345,7 +20480,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord14 = (value) => typeof value ==
20345
20480
  appBuild: manifest.appBuild,
20346
20481
  appId: manifest.appId,
20347
20482
  bytes,
20348
- engine: "capacitor",
20483
+ engine: options.config.engine,
20349
20484
  format: ABSOLUTE_ANDROID_RELEASE_FORMAT,
20350
20485
  platform: "android",
20351
20486
  releaseId,
@@ -20841,7 +20976,7 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
20841
20976
  throw new TypeError("Google Play publisher returned an invalid Android versionCode.");
20842
20977
  }
20843
20978
  return versionCode;
20844
- }, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isPublisher = (value) => isRecord15(value) && typeof value.publish === "function", publisherModulePath = (projectRoot, requested) => {
20979
+ }, isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isPublisher = (value) => isRecord16(value) && typeof value.publish === "function", publisherModulePath = (projectRoot, requested) => {
20845
20980
  const root = resolve41(projectRoot);
20846
20981
  const path = resolve41(root, requested);
20847
20982
  const projectRelative = relative28(root, path);
@@ -20855,7 +20990,7 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
20855
20990
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
20856
20991
  });
20857
20992
  const loaded = await import(pathToFileURL2(modulePath).href);
20858
- const publisher = isRecord15(loaded) ? loaded.default ?? loaded.registry : undefined;
20993
+ const publisher = isRecord16(loaded) ? loaded.default ?? loaded.registry : undefined;
20859
20994
  if (!isPublisher(publisher)) {
20860
20995
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
20861
20996
  }
@@ -20957,7 +21092,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (va
20957
21092
  let capabilityIssue;
20958
21093
  let plugins = [];
20959
21094
  try {
20960
- const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot);
21095
+ const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot, config.engine);
20961
21096
  currentCapabilities = plan.capabilities;
20962
21097
  plugins = plan.requiredPackages;
20963
21098
  } catch {
@@ -21410,8 +21545,10 @@ ${releaseAuditSteps("ios")}
21410
21545
  ...custom
21411
21546
  ], createAbsoluteMobileGithubWorkflow = (options) => {
21412
21547
  const platforms = [
21413
- ...options.config.platforms
21548
+ ...options.config.engine === "expo" ? options.config.platforms.filter((platform6) => platform6 === "android") : options.config.platforms
21414
21549
  ].sort();
21550
+ if (platforms.length === 0)
21551
+ throw new TypeError("Generated Expo production CI currently requires android in mobile.platforms; Expo iOS release automation is the next checkpoint.");
21415
21552
  const includePublishing = options.includePublishing === true;
21416
21553
  const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
21417
21554
  const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
@@ -21486,7 +21623,7 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
21486
21623
  changed: previous !== generated.workflow,
21487
21624
  format: ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
21488
21625
  path,
21489
- platforms: [...options.config.platforms].sort(),
21626
+ platforms: options.config.engine === "expo" ? options.config.platforms.filter((platform6) => platform6 === "android") : [...options.config.platforms].sort(),
21490
21627
  publishing: options.includePublishing === true,
21491
21628
  requiredSecrets: generated.requiredSecrets
21492
21629
  };
@@ -21545,14 +21682,14 @@ __export(exports_mobile, {
21545
21682
  import { access as access16, mkdir as mkdir16, readFile as readFile24, writeFile as writeFile18 } from "fs/promises";
21546
21683
  import { join as join56, relative as relative31, resolve as resolve44 } from "path";
21547
21684
  import { createInterface } from "readline/promises";
21548
- var NOT_FOUND4 = -1, isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
21685
+ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
21549
21686
  const manifest = JSON.parse(await readFile24(join56(projectRoot, "package.json"), "utf8"));
21550
- if (!isRecord16(manifest))
21687
+ if (!isRecord17(manifest))
21551
21688
  throw new TypeError("Application package.json must contain an object.");
21552
21689
  const names = new Set;
21553
21690
  for (const field of ["dependencies", "devDependencies"]) {
21554
21691
  const dependencies = Reflect.get(manifest, field);
21555
- if (isRecord16(dependencies))
21692
+ if (isRecord17(dependencies))
21556
21693
  for (const name of Object.keys(dependencies))
21557
21694
  names.add(name);
21558
21695
  }
@@ -21560,7 +21697,7 @@ var NOT_FOUND4 = -1, isRecord16 = (value) => typeof value === "object" && value
21560
21697
  }, resolvedPackageVersion = async (projectRoot, packageName) => {
21561
21698
  try {
21562
21699
  const manifest = JSON.parse(await readFile24(join56(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
21563
- return isRecord16(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
21700
+ return isRecord17(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
21564
21701
  } catch {
21565
21702
  return;
21566
21703
  }
@@ -21609,13 +21746,13 @@ var NOT_FOUND4 = -1, isRecord16 = (value) => typeof value === "object" && value
21609
21746
  }
21610
21747
  }, runCapacitor = async (projectRoot, args) => {
21611
21748
  const executable = await capacitorExecutable(projectRoot);
21612
- const process2 = Bun.spawn([executable, ...args], {
21749
+ const subprocess = Bun.spawn([executable, ...args], {
21613
21750
  cwd: projectRoot,
21614
21751
  stderr: "inherit",
21615
21752
  stdin: "inherit",
21616
21753
  stdout: "inherit"
21617
21754
  });
21618
- const exitCode = await process2.exited;
21755
+ const exitCode = await subprocess.exited;
21619
21756
  if (exitCode !== 0) {
21620
21757
  throw new TypeError(`Capacitor exited with status ${exitCode}.`);
21621
21758
  }
@@ -21627,15 +21764,26 @@ var NOT_FOUND4 = -1, isRecord16 = (value) => typeof value === "object" && value
21627
21764
  } catch {
21628
21765
  throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
21629
21766
  }
21630
- }, runExpo = async (project, args) => {
21767
+ }, expoProductionEnvironment = () => {
21768
+ const env6 = { ...process.env };
21769
+ delete env6.ABSOLUTE_EXPO_DEVELOPMENT;
21770
+ delete env6.ABSOLUTE_EXPO_DEVELOPMENT_CA_PATH;
21771
+ delete env6.EXPO_PUBLIC_ABSOLUTE_DEV_ANDROID_ORIGIN;
21772
+ delete env6.EXPO_PUBLIC_ABSOLUTE_DEV_IOS_ORIGIN;
21773
+ env6.BABEL_ENV = "production";
21774
+ env6.NODE_ENV = "production";
21775
+ return env6;
21776
+ }, runExpo = async (project, args, options = {}) => {
21631
21777
  const executable = await expoExecutable(project);
21632
- const process2 = Bun.spawn([executable, ...args], {
21778
+ const env6 = options.production ? expoProductionEnvironment() : { ...process.env };
21779
+ const subprocess = Bun.spawn([executable, ...args], {
21633
21780
  cwd: project,
21781
+ env: env6,
21634
21782
  stderr: "inherit",
21635
21783
  stdin: "inherit",
21636
21784
  stdout: "inherit"
21637
21785
  });
21638
- const exitCode = await process2.exited;
21786
+ const exitCode = await subprocess.exited;
21639
21787
  if (exitCode !== 0)
21640
21788
  throw new TypeError(`Expo exited with status ${exitCode}.`);
21641
21789
  }, ensureExpoPackages = async (project, args) => {
@@ -21682,10 +21830,9 @@ var NOT_FOUND4 = -1, isRecord16 = (value) => typeof value === "object" && value
21682
21830
  }, requireCapacitorEngine = (mobile, command) => {
21683
21831
  if (mobile.engine === "capacitor")
21684
21832
  return;
21685
- throw new TypeError(`${command} is not available for the experimental Expo engine yet. Use mobile init/sync and Expo CLI from the generated shell; Capacitor remains the release-capable engine.`);
21833
+ throw new TypeError(`${command} is not available for the Expo engine yet.`);
21686
21834
  }, inspectMobile = async (args) => {
21687
21835
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
21688
- requireCapacitorEngine(mobile, "mobile inspect");
21689
21836
  const report = await inspectAbsoluteMobileProject(mobile, projectRoot, {
21690
21837
  absolutejsVersion: await absolutejsVersionForReport()
21691
21838
  });
@@ -21741,7 +21888,7 @@ var NOT_FOUND4 = -1, isRecord16 = (value) => typeof value === "object" && value
21741
21888
  }, initialize = async (args) => {
21742
21889
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
21743
21890
  if (mobile.engine === "expo") {
21744
- console.warn("Experimental: Expo Auth and authenticated HTTP are available; Sync, release publishing, and physical-device acceptance are not complete.");
21891
+ console.warn("Experimental: Expo Android builds and publishing are available; iOS releases and physical-device acceptance are not complete.");
21745
21892
  const generated2 = await writeAbsoluteExpoProject(mobile, {
21746
21893
  force: args.includes("--force"),
21747
21894
  projectRoot
@@ -21835,7 +21982,6 @@ var NOT_FOUND4 = -1, isRecord16 = (value) => typeof value === "object" && value
21835
21982
  throw new TypeError("Usage: absolute mobile ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] [--config path]");
21836
21983
  const configPath2 = valueAfter(args, "--config");
21837
21984
  const { mobile, projectRoot } = await loadMobile(configPath2);
21838
- requireCapacitorEngine(mobile, "mobile ci github");
21839
21985
  const result = await writeAbsoluteMobileGithubWorkflow({
21840
21986
  config: mobile,
21841
21987
  configPath: configPath2,
@@ -21901,7 +22047,6 @@ var NOT_FOUND4 = -1, isRecord16 = (value) => typeof value === "object" && value
21901
22047
  }
21902
22048
  }, runReleaseDoctor = async (args) => {
21903
22049
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
21904
- requireCapacitorEngine(mobile, "mobile doctor release");
21905
22050
  const platform6 = args.find((value) => value === "android" || value === "ios");
21906
22051
  const effectiveMobile = platform6 ? { ...mobile, platforms: [platform6] } : mobile;
21907
22052
  const result = await inspectAbsoluteMobileRelease(effectiveMobile, projectRoot);
@@ -22083,31 +22228,37 @@ Mobile release security and compliance checks failed.`);
22083
22228
  keystorePath,
22084
22229
  storePasswordEnvironment: "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD"
22085
22230
  };
22086
- }, buildAndroid = async (args, prepareVersionCode) => {
22231
+ }, prepareExpoAndroidReleaseProject = async (mobile, projectRoot, args) => {
22232
+ await writeAbsoluteExpoProject(mobile, { projectRoot });
22233
+ await ensureExpoPackages(mobile.nativeProjectDirectory, [...args, "--yes"]);
22234
+ await syncAbsoluteExpoWebAssets(mobile);
22235
+ await runExpo(mobile.nativeProjectDirectory, ["prebuild", "--clean", "--no-install", "--platform", "android"], { production: true });
22236
+ }, prepareCapacitorAndroidReleaseProject = async (mobile, projectRoot) => {
22237
+ await writeAbsoluteCapacitorConfig(mobile, { projectRoot });
22238
+ await runCapacitorForPlatforms(projectRoot, "sync", ["android"]);
22239
+ await applyAbsoluteNativeDeepLinks(mobile, ["android"]);
22240
+ await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, [
22241
+ "android"
22242
+ ]);
22243
+ await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, ["android"]);
22244
+ }, prepareAndroidReleaseProject = (mobile, projectRoot, args) => mobile.engine === "expo" ? prepareExpoAndroidReleaseProject(mobile, projectRoot, args) : prepareCapacitorAndroidReleaseProject(mobile, projectRoot), buildAndroid = async (args, prepareVersionCode) => {
22087
22245
  const configPath2 = valueAfter(args, "--config");
22088
22246
  const { mobile, projectRoot } = await loadMobile(configPath2);
22089
- requireCapacitorEngine(mobile, "mobile build android");
22090
22247
  if (!mobile.platforms.includes("android")) {
22091
22248
  throw new TypeError("mobile build android requires android in mobile.platforms.");
22092
22249
  }
22093
22250
  const startedAt = performance.now();
22094
22251
  let success = false;
22095
22252
  try {
22096
- await repairAbsoluteAndroidDevSession(projectRoot);
22253
+ if (mobile.engine === "capacitor")
22254
+ await repairAbsoluteAndroidDevSession(projectRoot);
22097
22255
  await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath2, { prepareOnly: true });
22098
- await writeAbsoluteCapacitorConfig(mobile, { projectRoot });
22099
- await runCapacitorForPlatforms(projectRoot, "sync", ["android"]);
22100
- await applyAbsoluteNativeDeepLinks(mobile, ["android"]);
22101
- await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, [
22102
- "android"
22103
- ]);
22104
- await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, [
22105
- "android"
22106
- ]);
22256
+ await prepareAndroidReleaseProject(mobile, projectRoot, args);
22107
22257
  await requireAndroidReleaseReady(mobile, projectRoot);
22108
22258
  const release = await buildAbsoluteAndroidRelease({
22109
22259
  allowUnsigned: args.includes("--unsigned"),
22110
22260
  config: mobile,
22261
+ ...mobile.engine === "expo" ? { env: expoProductionEnvironment() } : {},
22111
22262
  outputDirectory: valueAfter(args, "--outdir"),
22112
22263
  projectRoot,
22113
22264
  signing: androidCiSigning(),
@@ -22122,7 +22273,7 @@ Mobile release security and compliance checks failed.`);
22122
22273
  } finally {
22123
22274
  sendTelemetryEvent("mobile:android-release-build", {
22124
22275
  durationMs: Math.round(performance.now() - startedAt),
22125
- engine: "capacitor",
22276
+ engine: mobile.engine,
22126
22277
  platform: "android",
22127
22278
  success,
22128
22279
  type: "aab",
@@ -22140,7 +22291,6 @@ Mobile release security and compliance checks failed.`);
22140
22291
  const configPath2 = valueAfter(args, "--config");
22141
22292
  const googlePlay = googlePlayTarget(args);
22142
22293
  const { mobile, projectRoot } = await loadMobile(configPath2);
22143
- requireCapacitorEngine(mobile, "mobile publish android");
22144
22294
  const startedAt = performance.now();
22145
22295
  let reused = false;
22146
22296
  let success = false;
@@ -22653,7 +22803,7 @@ Emulator setup verification:`);
22653
22803
  return;
22654
22804
  });
22655
22805
  const status2 = response?.ok ? await response.json().catch(() => null) : null;
22656
- const targets = isRecord16(status2) && isRecord16(status2.connectedTargets) ? status2.connectedTargets : undefined;
22806
+ const targets = isRecord17(status2) && isRecord17(status2.connectedTargets) ? status2.connectedTargets : undefined;
22657
22807
  if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
22658
22808
  return;
22659
22809
  await Bun.sleep(100);
@@ -1653,6 +1653,7 @@ import {
1653
1653
  mkdir as mkdir3,
1654
1654
  mkdtemp as mkdtemp2,
1655
1655
  readFile as readFile4,
1656
+ realpath as realpath2,
1656
1657
  rename as rename4,
1657
1658
  rm as rm3,
1658
1659
  stat,
@@ -2283,6 +2284,18 @@ var signAab = (artifactPath, capture, jarsigner, signing) => {
2283
2284
  throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
2284
2285
  };
2285
2286
  var sha256File = async (path) => createHash4("sha256").update(await readFile4(path)).digest("hex");
2287
+ var fingerprintExpoAndroidProject = async (nativeDirectory) => {
2288
+ const root = await realpath2(nativeDirectory);
2289
+ const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
2290
+ const records = await Promise.all(files.filter((path) => {
2291
+ const parts = path.replaceAll("\\", "/").split("/");
2292
+ return !parts.includes(".gradle") && !parts.includes("build");
2293
+ }).sort().map(async (path) => {
2294
+ const contents = await readFile4(join4(root, path));
2295
+ return `${path.replaceAll("\\", "/")}\x00${createHash4("sha256").update(contents).digest("hex")}\x00`;
2296
+ }));
2297
+ return createHash4("sha256").update(records.join("")).digest("hex");
2298
+ };
2286
2299
  var safeOutputDirectory = (projectRoot, requested) => {
2287
2300
  const root = resolve3(projectRoot);
2288
2301
  const output = resolve3(root, requested ?? ".absolutejs/mobile/releases/android");
@@ -2344,6 +2357,9 @@ var buildAbsoluteAndroidRelease = async (options) => {
2344
2357
  }
2345
2358
  const projectRoot = resolve3(options.projectRoot);
2346
2359
  const host = options.host ?? detectAbsoluteMobileHost();
2360
+ if (options.config.engine === "expo" && host === "wsl") {
2361
+ 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.");
2362
+ }
2347
2363
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
2348
2364
  const nativeDirectory = join4(options.config.nativeProjectDirectory, "android");
2349
2365
  const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
@@ -2352,7 +2368,9 @@ var buildAbsoluteAndroidRelease = async (options) => {
2352
2368
  }
2353
2369
  let { versionCode } = options;
2354
2370
  if (options.prepareVersionCode) {
2355
- const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
2371
+ const nativeFingerprint = options.config.engine === "expo" ? await fingerprintExpoAndroidProject(nativeDirectory) : await fingerprintAbsoluteAndroidNativeProject({
2372
+ nativeDirectory
2373
+ });
2356
2374
  const buildIdentity = createHash4("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
2357
2375
  versionCode = await options.prepareVersionCode(buildIdentity);
2358
2376
  }
@@ -2361,6 +2379,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
2361
2379
  }
2362
2380
  const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
2363
2381
  capture: options.capture,
2382
+ env: options.env,
2364
2383
  gradleArguments: versionCode === undefined ? [] : [`-Pandroid.injected.version.code=${versionCode}`],
2365
2384
  project: {
2366
2385
  androidRoot,
@@ -2401,7 +2420,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
2401
2420
  appBuild: manifest.appBuild,
2402
2421
  appId: manifest.appId,
2403
2422
  bytes,
2404
- engine: "capacitor",
2423
+ engine: options.config.engine,
2405
2424
  format: ABSOLUTE_ANDROID_RELEASE_FORMAT,
2406
2425
  platform: "android",
2407
2426
  releaseId,
@@ -8687,8 +8706,10 @@ var requiredSecrets = (platforms, includePublishing, custom) => [
8687
8706
  ];
8688
8707
  var createAbsoluteMobileGithubWorkflow = (options) => {
8689
8708
  const platforms = [
8690
- ...options.config.platforms
8709
+ ...options.config.engine === "expo" ? options.config.platforms.filter((platform) => platform === "android") : options.config.platforms
8691
8710
  ].sort();
8711
+ if (platforms.length === 0)
8712
+ throw new TypeError("Generated Expo production CI currently requires android in mobile.platforms; Expo iOS release automation is the next checkpoint.");
8692
8713
  const includePublishing = options.includePublishing === true;
8693
8714
  const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
8694
8715
  const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
@@ -8764,7 +8785,7 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
8764
8785
  changed: previous !== generated.workflow,
8765
8786
  format: ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
8766
8787
  path,
8767
- platforms: [...options.config.platforms].sort(),
8788
+ platforms: options.config.engine === "expo" ? options.config.platforms.filter((platform) => platform === "android") : [...options.config.platforms].sort(),
8768
8789
  publishing: options.includePublishing === true,
8769
8790
  requiredSecrets: generated.requiredSecrets
8770
8791
  };
@@ -10494,5 +10515,5 @@ export {
10494
10515
  writeAbsoluteMobileGithubWorkflow
10495
10516
  };
10496
10517
 
10497
- //# debugId=4795A6FB7632A5BE64756E2164756E21
10518
+ //# debugId=9EFFDF4D91097A2464756E2164756E21
10498
10519
  //# sourceMappingURL=index.js.map