@absolutejs/absolute 0.20.0-beta.103 → 0.20.0-beta.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/changelog.json +33 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/{compile-4xbpm6hf.js → compile-hsbs1xf6.js} +2 -2
- package/dist/cli/{dev-m4r4zs2k.js → dev-9cp5t2n1.js} +4 -4
- package/dist/cli/{expoProject-780thyfd.js → expoProject-zkprmjjz.js} +1 -1
- package/dist/cli/{index-d5fr1xp3.js → index-0zcebhm4.js} +59 -1
- package/dist/cli/{index-wpfvpsk7.js → index-5skbydh0.js} +1 -1
- package/dist/cli/{index-3sdvzrv2.js → index-7fxrgx5q.js} +1 -1
- package/dist/cli/{index-6ff78meg.js → index-hrzd7e5x.js} +10 -2
- package/dist/cli/{index-rvpd4w9t.js → index-mazxtfh4.js} +1 -1
- package/dist/cli/{index-qxnptvwn.js → index-q1d4ns4n.js} +100 -9
- package/dist/cli/index.js +5 -5
- package/dist/cli/{iosNativeWatcher-3k715hq0.js → iosNativeWatcher-t6gv0p1w.js} +2 -2
- package/dist/cli/{iosSimulatorController-dr98a0p9.js → iosSimulatorController-5n2a9fxv.js} +2 -2
- package/dist/cli/{mobile-6xe6zysv.js → mobile-fbtq3s5x.js} +1240 -370
- package/dist/cli/{remoteMacProtocol-kqvkp0qd.js → remoteMacProtocol-bbramzh6.js} +2 -2
- package/dist/cli/{start-nmyy64tr.js → start-5grq7nfr.js} +3 -3
- package/dist/mobile/index.js +1340 -571
- package/dist/mobile/index.js.map +10 -8
- package/dist/mobile/remoteMacAgentEntry.js +302 -255
- package/dist/src/mobile/index.d.ts +2 -0
- package/dist/src/mobile/iosRelease.d.ts +31 -7
- package/dist/src/mobile/iosReleaseAcceptance.d.ts +63 -0
- package/dist/src/mobile/iosTestReport.d.ts +1 -0
- package/dist/src/mobile/nativeReleaseReadiness.d.ts +5 -0
- package/dist/src/mobile/nativeTestReport.d.ts +15 -0
- package/dist/src/mobile/releaseDoctor.d.ts +2 -0
- package/dist/src/mobile/remoteMacProtocol.d.ts +7 -1
- package/dist/src/mobile/shellReleaseReadiness.d.ts +2 -0
- package/package.json +2 -1
|
@@ -7,12 +7,12 @@ import {
|
|
|
7
7
|
} from "./index-zh6hhrwy.js";
|
|
8
8
|
import {
|
|
9
9
|
start
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-mazxtfh4.js";
|
|
11
11
|
import {
|
|
12
12
|
ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
|
|
13
13
|
ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
|
|
14
14
|
resolveAbsoluteMobileRoute
|
|
15
|
-
} from "./index-
|
|
15
|
+
} from "./index-hrzd7e5x.js";
|
|
16
16
|
import"./index-bzs19w3r.js";
|
|
17
17
|
import"./index-s2tazax7.js";
|
|
18
18
|
import {
|
|
@@ -30,15 +30,17 @@ import {
|
|
|
30
30
|
listAbsoluteRemoteMacProfiles,
|
|
31
31
|
pairAbsoluteRemoteMac,
|
|
32
32
|
removeAbsoluteRemoteMacProfile
|
|
33
|
-
} from "./index-
|
|
33
|
+
} from "./index-7fxrgx5q.js";
|
|
34
34
|
import {
|
|
35
35
|
ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
36
36
|
parseIosSimulators,
|
|
37
37
|
repairAbsoluteIosDevSession
|
|
38
|
-
} from "./index-
|
|
38
|
+
} from "./index-5skbydh0.js";
|
|
39
39
|
import {
|
|
40
|
-
buildAbsoluteIosRelease
|
|
41
|
-
|
|
40
|
+
buildAbsoluteIosRelease,
|
|
41
|
+
requireAbsoluteIosReleaseMetadata,
|
|
42
|
+
resolveAbsoluteIosXcodeProject
|
|
43
|
+
} from "./index-q1d4ns4n.js";
|
|
42
44
|
import"./index-fc9vxw56.js";
|
|
43
45
|
import {
|
|
44
46
|
listLiveInstances
|
|
@@ -67,7 +69,7 @@ import {
|
|
|
67
69
|
syncAbsoluteExpoWebAssets,
|
|
68
70
|
unsignedAbsoluteMobileUpdate,
|
|
69
71
|
writeAbsoluteExpoProject
|
|
70
|
-
} from "./index-
|
|
72
|
+
} from "./index-0zcebhm4.js";
|
|
71
73
|
import {
|
|
72
74
|
buildAbsoluteExpoAndroidWslBundle
|
|
73
75
|
} from "./index-28nxv57v.js";
|
|
@@ -4412,9 +4414,9 @@ var require_sha256 = __commonJS(function(exports, module) {
|
|
|
4412
4414
|
var forge = require_forge();
|
|
4413
4415
|
require_md();
|
|
4414
4416
|
require_util();
|
|
4415
|
-
var
|
|
4416
|
-
forge.md.sha256 = forge.md.algorithms.sha256 =
|
|
4417
|
-
|
|
4417
|
+
var sha2564 = module.exports = forge.sha256 = forge.sha256 || {};
|
|
4418
|
+
forge.md.sha256 = forge.md.algorithms.sha256 = sha2564;
|
|
4419
|
+
sha2564.create = function() {
|
|
4418
4420
|
if (!_initialized) {
|
|
4419
4421
|
_init();
|
|
4420
4422
|
}
|
|
@@ -16776,15 +16778,15 @@ var require_main = __commonJS(function(exports) {
|
|
|
16776
16778
|
|
|
16777
16779
|
// src/cli/scripts/mobile.ts
|
|
16778
16780
|
import {
|
|
16779
|
-
access as
|
|
16780
|
-
mkdir as
|
|
16781
|
-
mkdtemp as
|
|
16782
|
-
readFile as
|
|
16783
|
-
rm as
|
|
16784
|
-
writeFile as
|
|
16781
|
+
access as access14,
|
|
16782
|
+
mkdir as mkdir15,
|
|
16783
|
+
mkdtemp as mkdtemp5,
|
|
16784
|
+
readFile as readFile19,
|
|
16785
|
+
rm as rm8,
|
|
16786
|
+
writeFile as writeFile16
|
|
16785
16787
|
} from "fs/promises";
|
|
16786
16788
|
import { createPublicKey as createPublicKey2 } from "crypto";
|
|
16787
|
-
import { join as
|
|
16789
|
+
import { join as join16, relative as relative13, resolve as resolve14 } from "path";
|
|
16788
16790
|
import { createInterface } from "readline/promises";
|
|
16789
16791
|
|
|
16790
16792
|
// src/mobile/nativeDeepLinks.ts
|
|
@@ -18454,14 +18456,228 @@ var applyAbsoluteNativeObservability = async (config, platforms = config.platfor
|
|
|
18454
18456
|
return { changed: changed.some(Boolean) };
|
|
18455
18457
|
};
|
|
18456
18458
|
|
|
18459
|
+
// src/mobile/nativeReleaseReadiness.ts
|
|
18460
|
+
import { mkdir as mkdir3, readFile as readFile6, readdir as readdir3, rename as rename6, writeFile as writeFile6 } from "fs/promises";
|
|
18461
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
18462
|
+
var START3 = "// absolutejs:release-readiness:start";
|
|
18463
|
+
var END3 = "// absolutejs:release-readiness:end";
|
|
18464
|
+
var IOS_PLUGIN_START = "// absolutejs:release-readiness-plugin:start";
|
|
18465
|
+
var IOS_PLUGIN_END = "// absolutejs:release-readiness-plugin:end";
|
|
18466
|
+
var ANDROID_PLUGIN3 = "AbsoluteReleaseReadinessPlugin.java";
|
|
18467
|
+
var optionalSource4 = async (path) => {
|
|
18468
|
+
try {
|
|
18469
|
+
return await readFile6(path, "utf8");
|
|
18470
|
+
} catch (error) {
|
|
18471
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
18472
|
+
return null;
|
|
18473
|
+
throw error;
|
|
18474
|
+
}
|
|
18475
|
+
};
|
|
18476
|
+
var writeChanged4 = async (path, source) => {
|
|
18477
|
+
const current = await optionalSource4(path);
|
|
18478
|
+
if (current === source)
|
|
18479
|
+
return false;
|
|
18480
|
+
await mkdir3(dirname3(path), { recursive: true });
|
|
18481
|
+
if (current === null) {
|
|
18482
|
+
await writeFile6(path, source, { flag: "wx" });
|
|
18483
|
+
return true;
|
|
18484
|
+
}
|
|
18485
|
+
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
18486
|
+
await writeFile6(temporary, source, { flag: "wx" });
|
|
18487
|
+
await rename6(temporary, path);
|
|
18488
|
+
return true;
|
|
18489
|
+
};
|
|
18490
|
+
var activityInRoot = async (root) => {
|
|
18491
|
+
let entries;
|
|
18492
|
+
try {
|
|
18493
|
+
entries = await readdir3(root, { recursive: true });
|
|
18494
|
+
} catch (error) {
|
|
18495
|
+
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
18496
|
+
return;
|
|
18497
|
+
throw error;
|
|
18498
|
+
}
|
|
18499
|
+
const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
|
|
18500
|
+
return candidate ? join6(root, candidate) : undefined;
|
|
18501
|
+
};
|
|
18502
|
+
var activityPath = async (config) => {
|
|
18503
|
+
const roots = [
|
|
18504
|
+
join6(config.nativeProjectDirectory, "android/app/src/main/java"),
|
|
18505
|
+
join6(config.nativeProjectDirectory, "android/app/src/main/kotlin")
|
|
18506
|
+
];
|
|
18507
|
+
const candidates = await Promise.all(roots.map(activityInRoot));
|
|
18508
|
+
const candidate = candidates.find((value) => value !== undefined);
|
|
18509
|
+
if (candidate)
|
|
18510
|
+
return candidate;
|
|
18511
|
+
throw new TypeError("Android MainActivity.java or MainActivity.kt was not found.");
|
|
18512
|
+
};
|
|
18513
|
+
var registrationRegion = (kotlin, generatedMethod) => {
|
|
18514
|
+
if (generatedMethod)
|
|
18515
|
+
return kotlin ? ` ${START3}
|
|
18516
|
+
override fun onCreate(savedInstanceState: android.os.Bundle?) {
|
|
18517
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
|
|
18518
|
+
super.onCreate(savedInstanceState)
|
|
18519
|
+
}
|
|
18520
|
+
${END3}
|
|
18521
|
+
` : ` ${START3}
|
|
18522
|
+
@Override
|
|
18523
|
+
public void onCreate(android.os.Bundle savedInstanceState) {
|
|
18524
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin.class);
|
|
18525
|
+
super.onCreate(savedInstanceState);
|
|
18526
|
+
}
|
|
18527
|
+
${END3}
|
|
18528
|
+
`;
|
|
18529
|
+
return kotlin ? ` ${START3}
|
|
18530
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin::class.java)
|
|
18531
|
+
${END3}
|
|
18532
|
+
` : ` ${START3}
|
|
18533
|
+
registerPlugin(AbsoluteReleaseReadinessPlugin.class);
|
|
18534
|
+
${END3}
|
|
18535
|
+
`;
|
|
18536
|
+
};
|
|
18537
|
+
var injectRegistration = (source) => {
|
|
18538
|
+
const kotlin = /\bfun\s+onCreate\s*\(/u.test(source) || source.includes("BridgeActivity()");
|
|
18539
|
+
const existingStart = source.indexOf(START3);
|
|
18540
|
+
const existingEnd = source.indexOf(END3);
|
|
18541
|
+
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
18542
|
+
throw new TypeError("AbsoluteJS release-readiness markers are malformed.");
|
|
18543
|
+
if (existingStart >= 0) {
|
|
18544
|
+
const from = source.lastIndexOf(`
|
|
18545
|
+
`, existingStart) + 1;
|
|
18546
|
+
const newline = source.indexOf(`
|
|
18547
|
+
`, existingEnd + END3.length);
|
|
18548
|
+
const through = newline < 0 ? source.length : newline + 1;
|
|
18549
|
+
const generatedMethod = source.slice(from, through).includes("onCreate(");
|
|
18550
|
+
return `${source.slice(0, from)}${registrationRegion(kotlin, generatedMethod)}${source.slice(through)}`;
|
|
18551
|
+
}
|
|
18552
|
+
const onCreate = kotlin ? source.search(/\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/u) : source.search(/\b(?:public|protected)\s+void\s+onCreate\s*\([^)]*\)\s*\{/u);
|
|
18553
|
+
if (onCreate >= 0) {
|
|
18554
|
+
const brace = source.indexOf("{", onCreate);
|
|
18555
|
+
const insert = source.indexOf(`
|
|
18556
|
+
`, brace) + 1;
|
|
18557
|
+
if (insert <= 0)
|
|
18558
|
+
throw new TypeError("Could not inject Android release readiness into MainActivity.");
|
|
18559
|
+
return `${source.slice(0, insert)}${registrationRegion(kotlin, false)}${source.slice(insert)}`;
|
|
18560
|
+
}
|
|
18561
|
+
const close = source.lastIndexOf("}");
|
|
18562
|
+
if (close < 0)
|
|
18563
|
+
throw new TypeError("Could not find the Android MainActivity class body.");
|
|
18564
|
+
const separator = source[close - 1] === `
|
|
18565
|
+
` ? "" : `
|
|
18566
|
+
`;
|
|
18567
|
+
return `${source.slice(0, close)}${separator}${registrationRegion(kotlin, true)}${source.slice(close)}`;
|
|
18568
|
+
};
|
|
18569
|
+
var pluginSource = (packageName) => `package ${packageName};
|
|
18570
|
+
|
|
18571
|
+
import android.util.Log;
|
|
18572
|
+
import com.getcapacitor.JSObject;
|
|
18573
|
+
import com.getcapacitor.Plugin;
|
|
18574
|
+
import com.getcapacitor.PluginCall;
|
|
18575
|
+
import com.getcapacitor.PluginMethod;
|
|
18576
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
18577
|
+
|
|
18578
|
+
@CapacitorPlugin(name = "AbsoluteReleaseReadiness")
|
|
18579
|
+
public final class AbsoluteReleaseReadinessPlugin extends Plugin {
|
|
18580
|
+
@PluginMethod
|
|
18581
|
+
public void ready(PluginCall call) {
|
|
18582
|
+
Log.i("AbsoluteJS", "Capacitor embedded web content ready");
|
|
18583
|
+
call.resolve(new JSObject().put("ready", true));
|
|
18584
|
+
}
|
|
18585
|
+
}
|
|
18586
|
+
`;
|
|
18587
|
+
var iosPluginRegion3 = `${IOS_PLUGIN_START}
|
|
18588
|
+
@objc(AbsoluteReleaseReadinessPlugin)
|
|
18589
|
+
public final class AbsoluteReleaseReadinessPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
18590
|
+
public let identifier = "AbsoluteReleaseReadinessPlugin"
|
|
18591
|
+
public let jsName = "AbsoluteReleaseReadiness"
|
|
18592
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
18593
|
+
CAPPluginMethod(name: "ready", returnType: CAPPluginReturnPromise)
|
|
18594
|
+
]
|
|
18595
|
+
|
|
18596
|
+
@objc public func ready(_ call: CAPPluginCall) {
|
|
18597
|
+
let info = Bundle.main.infoDictionary ?? [:]
|
|
18598
|
+
let version = info["CFBundleShortVersionString"] as? String ?? "unknown"
|
|
18599
|
+
let build = info["CFBundleVersion"] as? String ?? "unknown"
|
|
18600
|
+
NSLog("AbsoluteJS: Capacitor embedded web content ready; version=%@; build=%@", version, build)
|
|
18601
|
+
call.resolve(["ready": true])
|
|
18602
|
+
}
|
|
18603
|
+
}
|
|
18604
|
+
${IOS_PLUGIN_END}
|
|
18605
|
+
`;
|
|
18606
|
+
var replaceIosPluginRegion = (appDelegate, existingStart, existingEnd) => {
|
|
18607
|
+
const from = appDelegate.lastIndexOf(`
|
|
18608
|
+
`, existingStart) + 1;
|
|
18609
|
+
const newline = appDelegate.indexOf(`
|
|
18610
|
+
`, existingEnd + IOS_PLUGIN_END.length);
|
|
18611
|
+
const through = newline < 0 ? appDelegate.length : newline + 1;
|
|
18612
|
+
return `${appDelegate.slice(0, from)}${iosPluginRegion3}${appDelegate.slice(through)}`;
|
|
18613
|
+
};
|
|
18614
|
+
var configureIos5 = async (config) => {
|
|
18615
|
+
const appDelegatePath = join6(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
18616
|
+
const capacitorConfigPath = join6(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
18617
|
+
const appDelegate = await readFile6(appDelegatePath, "utf8");
|
|
18618
|
+
const existingStart = appDelegate.indexOf(IOS_PLUGIN_START);
|
|
18619
|
+
const existingEnd = appDelegate.indexOf(IOS_PLUGIN_END);
|
|
18620
|
+
if (existingStart < 0 !== existingEnd < 0 || existingStart >= 0 && existingEnd < existingStart)
|
|
18621
|
+
throw new TypeError("AbsoluteJS iOS release-readiness markers are malformed.");
|
|
18622
|
+
const source = existingStart < 0 ? `${appDelegate.trimEnd()}
|
|
18623
|
+
|
|
18624
|
+
${iosPluginRegion3}` : replaceIosPluginRegion(appDelegate, existingStart, existingEnd);
|
|
18625
|
+
let capacitorConfig;
|
|
18626
|
+
try {
|
|
18627
|
+
capacitorConfig = JSON.parse(await readFile6(capacitorConfigPath, "utf8"));
|
|
18628
|
+
} catch {
|
|
18629
|
+
throw new TypeError("iOS capacitor.config.json is invalid; run Capacitor sync before projecting release readiness.");
|
|
18630
|
+
}
|
|
18631
|
+
if (typeof capacitorConfig !== "object" || capacitorConfig === null)
|
|
18632
|
+
throw new TypeError("iOS capacitor.config.json must contain an object.");
|
|
18633
|
+
const packageClassList = Reflect.get(capacitorConfig, "packageClassList");
|
|
18634
|
+
if (!Array.isArray(packageClassList))
|
|
18635
|
+
throw new TypeError("iOS capacitor.config.json has no packageClassList; run Capacitor sync before projecting release readiness.");
|
|
18636
|
+
const classes = packageClassList.filter((value) => typeof value === "string");
|
|
18637
|
+
const nextConfig = `${JSON.stringify({
|
|
18638
|
+
...capacitorConfig,
|
|
18639
|
+
packageClassList: [
|
|
18640
|
+
...new Set([...classes, "AbsoluteReleaseReadinessPlugin"])
|
|
18641
|
+
]
|
|
18642
|
+
}, null, "\t")}
|
|
18643
|
+
`;
|
|
18644
|
+
const changed = await Promise.all([
|
|
18645
|
+
writeChanged4(appDelegatePath, source),
|
|
18646
|
+
writeChanged4(capacitorConfigPath, nextConfig)
|
|
18647
|
+
]);
|
|
18648
|
+
return changed.some(Boolean);
|
|
18649
|
+
};
|
|
18650
|
+
var configureAndroid5 = async (config) => {
|
|
18651
|
+
const mainActivityPath = await activityPath(config);
|
|
18652
|
+
const activity = await readFile6(mainActivityPath, "utf8");
|
|
18653
|
+
const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
|
|
18654
|
+
if (!packageName)
|
|
18655
|
+
throw new TypeError("Android MainActivity package declaration was not found.");
|
|
18656
|
+
const pluginPath = join6(config.nativeProjectDirectory, "android/app/src/main/java", packageName.replaceAll(".", "/"), ANDROID_PLUGIN3);
|
|
18657
|
+
const changed = await Promise.all([
|
|
18658
|
+
writeChanged4(mainActivityPath, injectRegistration(activity)),
|
|
18659
|
+
writeChanged4(pluginPath, pluginSource(packageName))
|
|
18660
|
+
]);
|
|
18661
|
+
return changed.some(Boolean);
|
|
18662
|
+
};
|
|
18663
|
+
var applyAbsoluteNativeReleaseReadiness = async (config, platforms = config.platforms) => {
|
|
18664
|
+
if (config.engine !== "capacitor")
|
|
18665
|
+
return { changed: false };
|
|
18666
|
+
const changed = await Promise.all([
|
|
18667
|
+
platforms.includes("android") ? configureAndroid5(config) : false,
|
|
18668
|
+
platforms.includes("ios") ? configureIos5(config) : false
|
|
18669
|
+
]);
|
|
18670
|
+
return { changed: changed.some(Boolean) };
|
|
18671
|
+
};
|
|
18672
|
+
|
|
18457
18673
|
// src/mobile/associationFiles.ts
|
|
18458
18674
|
import {
|
|
18459
18675
|
access,
|
|
18460
|
-
mkdir as
|
|
18461
|
-
readFile as
|
|
18462
|
-
rename as
|
|
18676
|
+
mkdir as mkdir4,
|
|
18677
|
+
readFile as readFile7,
|
|
18678
|
+
rename as rename7,
|
|
18463
18679
|
rm as rm3,
|
|
18464
|
-
writeFile as
|
|
18680
|
+
writeFile as writeFile7
|
|
18465
18681
|
} from "fs/promises";
|
|
18466
18682
|
import { resolve } from "path";
|
|
18467
18683
|
import { Elysia } from "elysia";
|
|
@@ -18525,7 +18741,7 @@ var createAbsoluteMobileAssociationDocuments = (config, options = {}) => {
|
|
|
18525
18741
|
var writeAtomic = async (path, source) => {
|
|
18526
18742
|
let current;
|
|
18527
18743
|
try {
|
|
18528
|
-
current = await
|
|
18744
|
+
current = await readFile7(path, "utf8");
|
|
18529
18745
|
} catch (error) {
|
|
18530
18746
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
18531
18747
|
throw error;
|
|
@@ -18534,8 +18750,8 @@ var writeAtomic = async (path, source) => {
|
|
|
18534
18750
|
if (current === source)
|
|
18535
18751
|
return false;
|
|
18536
18752
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
18537
|
-
await
|
|
18538
|
-
await
|
|
18753
|
+
await writeFile7(temporary, source, { flag: "wx" });
|
|
18754
|
+
await rename7(temporary, path);
|
|
18539
18755
|
return true;
|
|
18540
18756
|
};
|
|
18541
18757
|
var exists = async (path) => {
|
|
@@ -18550,7 +18766,7 @@ var assertOwnedOutput = async (root) => {
|
|
|
18550
18766
|
const path = resolve(root, OWNERSHIP_FILE);
|
|
18551
18767
|
let ownership;
|
|
18552
18768
|
try {
|
|
18553
|
-
ownership = JSON.parse(await
|
|
18769
|
+
ownership = JSON.parse(await readFile7(path, "utf8"));
|
|
18554
18770
|
} catch {
|
|
18555
18771
|
throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
|
|
18556
18772
|
}
|
|
@@ -18564,12 +18780,12 @@ var publishGeneratedDirectory = async (temporary, root) => {
|
|
|
18564
18780
|
await assertOwnedOutput(root);
|
|
18565
18781
|
const backup = `${root}.${crypto.randomUUID()}.previous`;
|
|
18566
18782
|
if (hasCurrent)
|
|
18567
|
-
await
|
|
18783
|
+
await rename7(root, backup);
|
|
18568
18784
|
try {
|
|
18569
|
-
await
|
|
18785
|
+
await rename7(temporary, root);
|
|
18570
18786
|
} catch (error) {
|
|
18571
18787
|
if (hasCurrent)
|
|
18572
|
-
await
|
|
18788
|
+
await rename7(backup, root);
|
|
18573
18789
|
throw error;
|
|
18574
18790
|
}
|
|
18575
18791
|
if (hasCurrent)
|
|
@@ -18577,7 +18793,7 @@ var publishGeneratedDirectory = async (temporary, root) => {
|
|
|
18577
18793
|
};
|
|
18578
18794
|
var materializeHost = async (root, host, files) => {
|
|
18579
18795
|
const directory = resolve(root, host, ".well-known");
|
|
18580
|
-
await
|
|
18796
|
+
await mkdir4(directory, { recursive: true });
|
|
18581
18797
|
return Promise.all(files.map(async ([name, document2]) => {
|
|
18582
18798
|
const path = resolve(directory, name);
|
|
18583
18799
|
await writeAtomic(path, `${JSON.stringify(document2, null, 2)}
|
|
@@ -18615,7 +18831,7 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
|
|
|
18615
18831
|
if (documents.apple) {
|
|
18616
18832
|
files.push(["apple-app-site-association", documents.apple]);
|
|
18617
18833
|
}
|
|
18618
|
-
await
|
|
18834
|
+
await mkdir4(temporary, { recursive: true });
|
|
18619
18835
|
try {
|
|
18620
18836
|
const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
|
|
18621
18837
|
await writeAtomic(resolve(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
|
|
@@ -18660,8 +18876,8 @@ var verifyAbsoluteMobileAssociationFiles = async (config, request = globalThis.f
|
|
|
18660
18876
|
};
|
|
18661
18877
|
|
|
18662
18878
|
// src/mobile/androidWebView.ts
|
|
18663
|
-
import { mkdir as
|
|
18664
|
-
import { dirname as
|
|
18879
|
+
import { mkdir as mkdir5, writeFile as writeFile8 } from "fs/promises";
|
|
18880
|
+
import { dirname as dirname4, resolve as resolve2 } from "path";
|
|
18665
18881
|
var CDP_COMMAND_TIMEOUT_MS = 1e4;
|
|
18666
18882
|
var WEBVIEW_ATTACH_TIMEOUT_MS = 30000;
|
|
18667
18883
|
var WEBVIEW_POLL_MS = 200;
|
|
@@ -18955,8 +19171,8 @@ var createSession = (options, capture, socket, hostPort, target, connection) =>
|
|
|
18955
19171
|
throw new Error("Android WebView screenshot returned no image data.");
|
|
18956
19172
|
}
|
|
18957
19173
|
const absolutePath = resolve2(path);
|
|
18958
|
-
await
|
|
18959
|
-
await
|
|
19174
|
+
await mkdir5(dirname4(absolutePath), { recursive: true });
|
|
19175
|
+
await writeFile8(absolutePath, Buffer.from(data, "base64"));
|
|
18960
19176
|
return absolutePath;
|
|
18961
19177
|
},
|
|
18962
19178
|
tap: async (coordinateX, coordinateY) => {
|
|
@@ -19103,13 +19319,13 @@ var waitForAbsoluteAndroidHmrApply = async (session, options) => {
|
|
|
19103
19319
|
};
|
|
19104
19320
|
|
|
19105
19321
|
// src/mobile/releaseDoctor.ts
|
|
19106
|
-
import { access as access4, readFile as
|
|
19107
|
-
import { dirname as
|
|
19322
|
+
import { access as access4, readFile as readFile9, readdir as readdir4 } from "fs/promises";
|
|
19323
|
+
import { dirname as dirname6, extname, join as join8, relative as relative3 } from "path";
|
|
19108
19324
|
|
|
19109
19325
|
// src/mobile/mobileBundleInspection.ts
|
|
19110
19326
|
import { createHash } from "crypto";
|
|
19111
|
-
import { access as access2, readFile as
|
|
19112
|
-
import { join as
|
|
19327
|
+
import { access as access2, readFile as readFile8, stat } from "fs/promises";
|
|
19328
|
+
import { join as join7, relative, resolve as resolve3 } from "path";
|
|
19113
19329
|
var MOBILE_FRAMEWORKS = new Set([
|
|
19114
19330
|
"angular",
|
|
19115
19331
|
"ember",
|
|
@@ -19134,7 +19350,7 @@ var pathExists = async (path) => {
|
|
|
19134
19350
|
}
|
|
19135
19351
|
};
|
|
19136
19352
|
var readObject = async (path) => {
|
|
19137
|
-
const value = JSON.parse(await
|
|
19353
|
+
const value = JSON.parse(await readFile8(path, "utf8"));
|
|
19138
19354
|
if (!isObject(value))
|
|
19139
19355
|
throw new TypeError("JSON root must be an object.");
|
|
19140
19356
|
return value;
|
|
@@ -19162,14 +19378,14 @@ var requireBundleFile = async (root, value, field, expectedHash) => {
|
|
|
19162
19378
|
if (expectedHash !== undefined) {
|
|
19163
19379
|
if (!SHA256_PATTERN.test(expectedHash))
|
|
19164
19380
|
throw new TypeError(`${field} has an invalid SHA-256 digest.`);
|
|
19165
|
-
const actual = createHash("sha256").update(await
|
|
19381
|
+
const actual = createHash("sha256").update(await readFile8(path)).digest("hex");
|
|
19166
19382
|
if (actual !== expectedHash)
|
|
19167
19383
|
throw new TypeError(`${field} failed its SHA-256 integrity check.`);
|
|
19168
19384
|
}
|
|
19169
19385
|
return portable;
|
|
19170
19386
|
};
|
|
19171
19387
|
var inspectAbsoluteMobileBundle = async (config, projectRoot) => {
|
|
19172
|
-
const manifestPath =
|
|
19388
|
+
const manifestPath = join7(config.bundleDirectory, "absolute-mobile-manifest.json");
|
|
19173
19389
|
const manifest = portablePath(projectRoot, manifestPath);
|
|
19174
19390
|
if (!await pathExists(manifestPath))
|
|
19175
19391
|
return { manifest, status: "missing" };
|
|
@@ -19266,8 +19482,8 @@ import {
|
|
|
19266
19482
|
createPublicKey,
|
|
19267
19483
|
X509Certificate
|
|
19268
19484
|
} from "crypto";
|
|
19269
|
-
import { access as access3, mkdir as
|
|
19270
|
-
import { dirname as
|
|
19485
|
+
import { access as access3, mkdir as mkdir6 } from "fs/promises";
|
|
19486
|
+
import { dirname as dirname5, isAbsolute, relative as relative2, resolve as resolve4, sep } from "path";
|
|
19271
19487
|
import { pathToFileURL } from "url";
|
|
19272
19488
|
import { Elysia as Elysia2 } from "elysia";
|
|
19273
19489
|
var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1;
|
|
@@ -19484,7 +19700,7 @@ var writeAbsoluteMobileUpdateRegistry = async (options) => {
|
|
|
19484
19700
|
return;
|
|
19485
19701
|
});
|
|
19486
19702
|
}
|
|
19487
|
-
await
|
|
19703
|
+
await mkdir6(dirname5(path), { recursive: true });
|
|
19488
19704
|
await Bun.write(path, renderAbsoluteMobileUpdateRegistry({
|
|
19489
19705
|
...options.health ? { health: options.health } : {},
|
|
19490
19706
|
...options.rollout ? { rollout: options.rollout } : {},
|
|
@@ -19527,14 +19743,14 @@ var inspectReleaseAsset = async (path, isDirectory, isFile) => {
|
|
|
19527
19743
|
return findHmrAsset(path);
|
|
19528
19744
|
if (!isFile || !RELEASE_ASSET_EXTENSIONS.has(extname(path)))
|
|
19529
19745
|
return;
|
|
19530
|
-
const source = await
|
|
19746
|
+
const source = await readFile9(path, "utf8");
|
|
19531
19747
|
return HMR_ASSET_PATTERN.test(source) ? path : undefined;
|
|
19532
19748
|
};
|
|
19533
19749
|
var findHmrAsset = async (root) => {
|
|
19534
19750
|
if (!await pathExists2(root))
|
|
19535
19751
|
return;
|
|
19536
|
-
const entries = await
|
|
19537
|
-
const matches = await Promise.all(entries.map((entry) => inspectReleaseAsset(
|
|
19752
|
+
const entries = await readdir4(root, { withFileTypes: true });
|
|
19753
|
+
const matches = await Promise.all(entries.map((entry) => inspectReleaseAsset(join8(root, entry.name), entry.isDirectory(), entry.isFile())));
|
|
19538
19754
|
return matches.find((match) => match !== undefined);
|
|
19539
19755
|
};
|
|
19540
19756
|
var pass = (id, detail, path) => ({ detail, id, path, status: "pass" });
|
|
@@ -19564,7 +19780,7 @@ var mobileUpdateServerCheck = async (config, projectRoot) => {
|
|
|
19564
19780
|
};
|
|
19565
19781
|
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19566
19782
|
var readJsonObject = async (path) => {
|
|
19567
|
-
const value = JSON.parse(await
|
|
19783
|
+
const value = JSON.parse(await readFile9(path, "utf8"));
|
|
19568
19784
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
19569
19785
|
throw new TypeError("JSON root must be an object.");
|
|
19570
19786
|
return Object.fromEntries(Object.entries(value));
|
|
@@ -19583,7 +19799,7 @@ var packageDeclarations = (manifest) => {
|
|
|
19583
19799
|
};
|
|
19584
19800
|
var versionCore = (version) => version.split("-")[0]?.split(".").slice(0, 2).join(".");
|
|
19585
19801
|
var capacitorVersionCheck = async (config, projectRoot) => {
|
|
19586
|
-
const manifestPath =
|
|
19802
|
+
const manifestPath = join8(projectRoot, "package.json");
|
|
19587
19803
|
try {
|
|
19588
19804
|
const manifest = await readJsonObject(manifestPath);
|
|
19589
19805
|
const declarations = packageDeclarations(manifest);
|
|
@@ -19596,7 +19812,7 @@ var capacitorVersionCheck = async (config, projectRoot) => {
|
|
|
19596
19812
|
const declared = declarations.get(name);
|
|
19597
19813
|
if (!declared || !EXACT_VERSION_PATTERN.test(declared))
|
|
19598
19814
|
throw new TypeError(`${name} must be a direct exact dependency.`);
|
|
19599
|
-
const installed = await readJsonObject(
|
|
19815
|
+
const installed = await readJsonObject(await findAbsoluteInstalledPackageManifest(projectRoot, name));
|
|
19600
19816
|
if (installed.version !== declared)
|
|
19601
19817
|
throw new TypeError(`${name} does not match its installed version.`);
|
|
19602
19818
|
return declared;
|
|
@@ -19619,7 +19835,7 @@ var satisfiesGeneratedVersion = (declared, installed) => {
|
|
|
19619
19835
|
return actual[0] === expected[0] && actual[1] === expected[1] && (actual[2] ?? NOT_FOUND3) >= (expected[2] ?? 0);
|
|
19620
19836
|
};
|
|
19621
19837
|
var expoVersionCheck = async (config) => {
|
|
19622
|
-
const manifestPath =
|
|
19838
|
+
const manifestPath = join8(config.nativeProjectDirectory, "package.json");
|
|
19623
19839
|
try {
|
|
19624
19840
|
const manifest = await readJsonObject(manifestPath);
|
|
19625
19841
|
const declarations = packageDeclarations(manifest);
|
|
@@ -19629,13 +19845,13 @@ var expoVersionCheck = async (config) => {
|
|
|
19629
19845
|
throw new TypeError(`Generated Expo project is missing ${missingRequired}.`);
|
|
19630
19846
|
const installedVersions = await Promise.all([...declarations].map(async ([name, declared]) => ({
|
|
19631
19847
|
declared,
|
|
19632
|
-
installed: await readJsonObject(
|
|
19848
|
+
installed: await readJsonObject(join8(config.nativeProjectDirectory, "node_modules", name, "package.json")),
|
|
19633
19849
|
name
|
|
19634
19850
|
})));
|
|
19635
19851
|
const mismatch = installedVersions.find(({ declared, installed }) => typeof installed.version !== "string" || !satisfiesGeneratedVersion(declared, installed.version));
|
|
19636
19852
|
if (mismatch)
|
|
19637
19853
|
throw new TypeError(`Generated Expo dependency ${mismatch.name}@${mismatch.declared} does not match its installed version.`);
|
|
19638
|
-
if (!await pathExists2(
|
|
19854
|
+
if (!await pathExists2(join8(config.nativeProjectDirectory, "bun.lock")))
|
|
19639
19855
|
throw new TypeError("Generated Expo dependency lockfile is missing.");
|
|
19640
19856
|
return pass("mobile.expo-versions", `Generated Expo SDK dependencies are pinned, installed, and locked (${declarations.get("expo")}).`, manifestPath);
|
|
19641
19857
|
} catch (error) {
|
|
@@ -19643,9 +19859,9 @@ var expoVersionCheck = async (config) => {
|
|
|
19643
19859
|
}
|
|
19644
19860
|
};
|
|
19645
19861
|
var dependencyLockAt = async (directory) => (await Promise.all(LOCK_FILES.map(async (name) => ({
|
|
19646
|
-
exists: await pathExists2(
|
|
19862
|
+
exists: await pathExists2(join8(directory, name)),
|
|
19647
19863
|
name,
|
|
19648
|
-
path:
|
|
19864
|
+
path: join8(directory, name)
|
|
19649
19865
|
})))).find(({ exists: exists2 }) => exists2);
|
|
19650
19866
|
var declaredWorkspacePatterns = (declared) => {
|
|
19651
19867
|
if (Array.isArray(declared))
|
|
@@ -19655,7 +19871,7 @@ var declaredWorkspacePatterns = (declared) => {
|
|
|
19655
19871
|
return [];
|
|
19656
19872
|
};
|
|
19657
19873
|
var ownsWorkspace = async (directory, projectRoot) => {
|
|
19658
|
-
const manifest = await readJsonObject(
|
|
19874
|
+
const manifest = await readJsonObject(join8(directory, "package.json")).catch(() => {
|
|
19659
19875
|
return;
|
|
19660
19876
|
});
|
|
19661
19877
|
const workspacePath = relative3(directory, projectRoot).replace(/\\/gu, "/");
|
|
@@ -19666,7 +19882,7 @@ var ancestorDirectories = (projectRoot) => {
|
|
|
19666
19882
|
let directory = projectRoot;
|
|
19667
19883
|
while (true) {
|
|
19668
19884
|
directories.push(directory);
|
|
19669
|
-
const parent =
|
|
19885
|
+
const parent = dirname6(directory);
|
|
19670
19886
|
if (parent === directory)
|
|
19671
19887
|
return directories;
|
|
19672
19888
|
directory = parent;
|
|
@@ -19681,6 +19897,18 @@ var findAbsoluteDependencyLock = async (projectRoot) => {
|
|
|
19681
19897
|
}));
|
|
19682
19898
|
return candidates.find((candidate) => candidate !== undefined);
|
|
19683
19899
|
};
|
|
19900
|
+
var findAbsoluteInstalledPackageManifest = async (projectRoot, name) => {
|
|
19901
|
+
const candidates = await Promise.all(ancestorDirectories(projectRoot).map(async (directory, index) => {
|
|
19902
|
+
if (index > 0 && !await ownsWorkspace(directory, projectRoot))
|
|
19903
|
+
return;
|
|
19904
|
+
const candidate = join8(directory, "node_modules", name, "package.json");
|
|
19905
|
+
return await pathExists2(candidate) ? candidate : undefined;
|
|
19906
|
+
}));
|
|
19907
|
+
const installed = candidates.find((candidate) => candidate !== undefined);
|
|
19908
|
+
if (installed)
|
|
19909
|
+
return installed;
|
|
19910
|
+
throw new TypeError(`${name} is not installed for this application.`);
|
|
19911
|
+
};
|
|
19684
19912
|
var dependencyLockCheck = async (projectRoot) => {
|
|
19685
19913
|
const present = await findAbsoluteDependencyLock(projectRoot);
|
|
19686
19914
|
return present ? pass("mobile.dependency-lock", `Dependency graph is locked by ${present.name}.`, present.path) : fail("mobile.dependency-lock", "No supported dependency lockfile is present.", projectRoot, "Install dependencies with the project package manager and commit its lockfile before release.");
|
|
@@ -19725,7 +19953,7 @@ var capacitorConfigReleaseCheck = async (nativeConfigPath) => {
|
|
|
19725
19953
|
if (!await pathExists2(nativeConfigPath)) {
|
|
19726
19954
|
return fail("android.capacitor-config", "The generated Android Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync android` before release validation.");
|
|
19727
19955
|
}
|
|
19728
|
-
const unsafe = isUnsafeCapacitorConfig(await
|
|
19956
|
+
const unsafe = isUnsafeCapacitorConfig(await readFile9(nativeConfigPath, "utf8"));
|
|
19729
19957
|
return unsafe ? fail("android.capacitor-config", "Android Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync android`; do not ship development transport overrides.") : pass("android.capacitor-config", "Android Capacitor config contains no development transport overrides.", nativeConfigPath);
|
|
19730
19958
|
};
|
|
19731
19959
|
var capacitorIdentityCheck = async (config, platform, nativeConfigPath) => {
|
|
@@ -19742,12 +19970,12 @@ var manifestReleaseCheck = async (manifestPath) => {
|
|
|
19742
19970
|
if (!await pathExists2(manifestPath)) {
|
|
19743
19971
|
return fail("android.cleartext", "The Android manifest is missing.", manifestPath, "Run `absolute mobile sync android` before release validation.");
|
|
19744
19972
|
}
|
|
19745
|
-
const source = await
|
|
19973
|
+
const source = await readFile9(manifestPath, "utf8");
|
|
19746
19974
|
const cleartext = /android:usesCleartextTraffic=["']true["']/u.test(source);
|
|
19747
19975
|
const networkConfigName = source.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
|
|
19748
|
-
const networkConfigPath = networkConfigName ?
|
|
19976
|
+
const networkConfigPath = networkConfigName ? join8(dirname6(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
|
|
19749
19977
|
const developmentTrustReference = /android:networkSecurityConfig=["']@xml\/absolutejs_dev_network_security["']/u.test(source);
|
|
19750
|
-
const developmentTrustContents = networkConfigPath ? await
|
|
19978
|
+
const developmentTrustContents = networkConfigPath ? await readFile9(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
|
|
19751
19979
|
const developmentTrust = developmentTrustReference || developmentTrustContents;
|
|
19752
19980
|
return cleartext || developmentTrust ? fail("android.cleartext", developmentTrust ? "Android still references the AbsoluteJS development certificate authority." : "Android explicitly permits cleartext traffic.", manifestPath, "Run `absolute mobile sync android`; do not ship development transport or trust overrides.") : pass("android.cleartext", "Android does not explicitly permit cleartext traffic.", manifestPath);
|
|
19753
19981
|
};
|
|
@@ -19758,13 +19986,13 @@ var hmrAssetsReleaseCheck = async (publicRoot) => {
|
|
|
19758
19986
|
var embeddedBundleReleaseCheck = async (config, projectRoot, platform, publicRoot) => {
|
|
19759
19987
|
const inspection = await inspectAbsoluteMobileBundle({ ...config, bundleDirectory: publicRoot }, projectRoot);
|
|
19760
19988
|
if (inspection.status === "valid")
|
|
19761
|
-
return pass(`${platform}.bundle-integrity`, `Packaged mobile manifest, runtime, routes, and ${inspection.pageCount ?? 0} page asset(s) passed structural and SHA-256 validation.`,
|
|
19762
|
-
return fail(`${platform}.bundle-integrity`, inspection.status === "missing" ? "The packaged mobile manifest is missing." : `The packaged mobile bundle is invalid: ${inspection.issue ?? "unknown validation error"}`,
|
|
19989
|
+
return pass(`${platform}.bundle-integrity`, `Packaged mobile manifest, runtime, routes, and ${inspection.pageCount ?? 0} page asset(s) passed structural and SHA-256 validation.`, join8(publicRoot, "absolute-mobile-manifest.json"));
|
|
19990
|
+
return fail(`${platform}.bundle-integrity`, inspection.status === "missing" ? "The packaged mobile manifest is missing." : `The packaged mobile bundle is invalid: ${inspection.issue ?? "unknown validation error"}`, join8(publicRoot, "absolute-mobile-manifest.json"), "Rebuild the production mobile bundle and run Capacitor sync for this platform.");
|
|
19763
19991
|
};
|
|
19764
19992
|
var contentSecurityPolicyCheck = async (config, platform, publicRoot) => {
|
|
19765
|
-
const path =
|
|
19993
|
+
const path = join8(publicRoot, "index.html");
|
|
19766
19994
|
try {
|
|
19767
|
-
const source = await
|
|
19995
|
+
const source = await readFile9(path, "utf8");
|
|
19768
19996
|
const requirements = [
|
|
19769
19997
|
"Content-Security-Policy",
|
|
19770
19998
|
"default-src 'self'",
|
|
@@ -19793,12 +20021,12 @@ var validateExpoCodeSigningProjection = async (config, expo) => {
|
|
|
19793
20021
|
return;
|
|
19794
20022
|
if (!generatedUpdates || generatedUpdates.codeSigningCertificate !== "./certs/absolute-mobile-update.pem" || !isRecord3(generatedUpdates.codeSigningMetadata) || generatedUpdates.codeSigningMetadata.keyid !== configuredSigning.keyId || generatedUpdates.codeSigningMetadata.alg !== configuredSigning.algorithm)
|
|
19795
20023
|
throw new TypeError("Generated Expo code-signing metadata does not match mobile config.");
|
|
19796
|
-
const generatedCertificate = await
|
|
20024
|
+
const generatedCertificate = await readFile9(join8(config.nativeProjectDirectory, "certs", "absolute-mobile-update.pem"), "utf8");
|
|
19797
20025
|
if (generatedCertificate !== configuredSigning.certificatePem)
|
|
19798
20026
|
throw new TypeError("Generated Expo code-signing certificate does not match mobile config.");
|
|
19799
20027
|
};
|
|
19800
20028
|
var expoApplicationConfigCheck = async (config, projectRoot) => {
|
|
19801
|
-
const path =
|
|
20029
|
+
const path = join8(config.nativeProjectDirectory, "app.json");
|
|
19802
20030
|
try {
|
|
19803
20031
|
const root = await readJsonObject(path);
|
|
19804
20032
|
if (!isRecord3(root.expo))
|
|
@@ -19821,12 +20049,12 @@ var expoApplicationConfigCheck = async (config, projectRoot) => {
|
|
|
19821
20049
|
}
|
|
19822
20050
|
};
|
|
19823
20051
|
var expoEmbeddedAssetsCheck = async (config) => {
|
|
19824
|
-
const generated =
|
|
19825
|
-
const assetsRoot =
|
|
20052
|
+
const generated = join8(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
|
|
20053
|
+
const assetsRoot = join8(config.nativeProjectDirectory, "assets", "absolute");
|
|
19826
20054
|
try {
|
|
19827
20055
|
const [source, manifest] = await Promise.all([
|
|
19828
|
-
|
|
19829
|
-
readJsonObject(
|
|
20056
|
+
readFile9(generated, "utf8"),
|
|
20057
|
+
readJsonObject(join8(config.bundleDirectory, "absolute-mobile-manifest.json"))
|
|
19830
20058
|
]);
|
|
19831
20059
|
if (source.includes("embedded AbsoluteJS bundle is unavailable") || typeof manifest.appBuild !== "string" || !source.includes(JSON.stringify(manifest.appBuild)) || !source.includes(JSON.stringify(config.productionOrigin))) {
|
|
19832
20060
|
throw new TypeError("Generated Expo assets do not contain the prepared production release identity.");
|
|
@@ -19841,7 +20069,7 @@ var expoEmbeddedAssetsCheck = async (config) => {
|
|
|
19841
20069
|
}))).sort();
|
|
19842
20070
|
if (sourceFiles.length === 0 || embeddedFiles.length !== 1)
|
|
19843
20071
|
throw new TypeError("Generated Expo asset count does not match the prepared mobile bundle.");
|
|
19844
|
-
const contents = await Promise.all(sourceFiles.map((path) =>
|
|
20072
|
+
const contents = await Promise.all(sourceFiles.map((path) => readFile9(join8(config.bundleDirectory, path))));
|
|
19845
20073
|
let offset = 0;
|
|
19846
20074
|
const entries = sourceFiles.map((path, index) => {
|
|
19847
20075
|
const entry = {
|
|
@@ -19852,7 +20080,7 @@ var expoEmbeddedAssetsCheck = async (config) => {
|
|
|
19852
20080
|
offset += entry.length;
|
|
19853
20081
|
return entry;
|
|
19854
20082
|
});
|
|
19855
|
-
const archive = await
|
|
20083
|
+
const archive = await readFile9(join8(assetsRoot, embeddedFiles[0] ?? ""));
|
|
19856
20084
|
if (!archive.equals(Buffer.concat(contents)) || entries.some((entry) => !source.includes(JSON.stringify(entry))))
|
|
19857
20085
|
throw new TypeError("Generated Expo asset bytes differ from the prepared mobile bundle.");
|
|
19858
20086
|
return pass("expo.bundle-projection", `Expo embeds the complete signed mobile bundle as one opaque archive containing ${sourceFiles.length} file(s).`, generated);
|
|
@@ -19864,20 +20092,20 @@ var sourceFiles = async (root, extensions) => {
|
|
|
19864
20092
|
if (!await pathExists2(root))
|
|
19865
20093
|
return [];
|
|
19866
20094
|
const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
|
|
19867
|
-
return files.filter((file) => extensions.has(extname(file))).map((file) =>
|
|
20095
|
+
return files.filter((file) => extensions.has(extname(file))).map((file) => join8(root, file));
|
|
19868
20096
|
};
|
|
19869
20097
|
var containsPattern = async (paths, pattern) => {
|
|
19870
|
-
const sources = await Promise.all(paths.map((path) =>
|
|
20098
|
+
const sources = await Promise.all(paths.map((path) => readFile9(path, "utf8")));
|
|
19871
20099
|
const index = sources.findIndex((source) => pattern.test(source));
|
|
19872
20100
|
return index === NOT_FOUND3 ? undefined : paths[index];
|
|
19873
20101
|
};
|
|
19874
20102
|
var androidNativeSecurityCheck = async (androidRoot) => {
|
|
19875
|
-
const manifestPath =
|
|
20103
|
+
const manifestPath = join8(androidRoot, "app/src/main/AndroidManifest.xml");
|
|
19876
20104
|
try {
|
|
19877
|
-
const manifest = await
|
|
20105
|
+
const manifest = await readFile9(manifestPath, "utf8");
|
|
19878
20106
|
if (/android:debuggable=["']true["']/u.test(manifest))
|
|
19879
20107
|
throw new TypeError("Android release manifest explicitly enables application debugging.");
|
|
19880
|
-
const sources = await sourceFiles(
|
|
20108
|
+
const sources = await sourceFiles(join8(androidRoot, "app/src/main"), new Set([".java", ".kt"]));
|
|
19881
20109
|
const debugSource = await containsPattern(sources, /setWebContentsDebuggingEnabled\s*\(\s*true\s*\)/u);
|
|
19882
20110
|
if (debugSource)
|
|
19883
20111
|
return fail("android.native-debugging", "Android application source unconditionally enables WebView debugging.", debugSource, "Remove the unconditional WebView debugging call; use the platform debug-build behavior during development.");
|
|
@@ -19887,7 +20115,7 @@ var androidNativeSecurityCheck = async (androidRoot) => {
|
|
|
19887
20115
|
}
|
|
19888
20116
|
};
|
|
19889
20117
|
var androidExportedComponentsCheck = async (manifestPath) => {
|
|
19890
|
-
const source = await
|
|
20118
|
+
const source = await readFile9(manifestPath, "utf8").catch(() => "");
|
|
19891
20119
|
const exported = [
|
|
19892
20120
|
...source.matchAll(/<(?:activity|activity-alias|provider|receiver|service)\b[^>]*>/giu)
|
|
19893
20121
|
].map(([tag]) => tag).filter((tag) => /android:exported=["']true["']/iu.test(tag)).map((tag) => tag.match(/android:name=["']([^"']+)["']/iu)?.[1]).filter((name) => Boolean(name) && name !== ".MainActivity");
|
|
@@ -19895,7 +20123,7 @@ var androidExportedComponentsCheck = async (manifestPath) => {
|
|
|
19895
20123
|
};
|
|
19896
20124
|
var androidDeepLinkProjectionCheck = async (config, manifestPath) => {
|
|
19897
20125
|
try {
|
|
19898
|
-
const source = await
|
|
20126
|
+
const source = await readFile9(manifestPath, "utf8");
|
|
19899
20127
|
const hasWebHost = (host) => [...source.matchAll(/<data\b[^>]*>/giu)].some(([tag]) => tag.includes('android:scheme="https"') && tag.includes(`android:host="${host}"`));
|
|
19900
20128
|
if (!source.includes('android:autoVerify="true"') || !source.includes("android.intent.category.BROWSABLE") || config.deepLinkHosts.some((host) => !hasWebHost(host)) || config.deepLinkScheme && !source.includes(`android:scheme="${config.deepLinkScheme}"`))
|
|
19901
20129
|
throw new TypeError("Android App Link or custom-scheme projection does not match mobile config.");
|
|
@@ -19909,15 +20137,15 @@ var nativeUpdateWatchdogCheck = async (config, platform) => {
|
|
|
19909
20137
|
return;
|
|
19910
20138
|
const timeout = String(config.updates.bootTimeoutMs);
|
|
19911
20139
|
if (platform === "ios") {
|
|
19912
|
-
const path =
|
|
19913
|
-
const configPath =
|
|
20140
|
+
const path = join8(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
20141
|
+
const configPath = join8(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
|
|
19914
20142
|
const [source, nativeConfig] = await Promise.all([
|
|
19915
|
-
|
|
19916
|
-
|
|
20143
|
+
readFile9(path, "utf8").catch(() => ""),
|
|
20144
|
+
readFile9(configPath, "utf8").catch(() => "")
|
|
19917
20145
|
]);
|
|
19918
20146
|
return source.includes("AbsoluteMobileUpdateWatchdogPlugin") && source.includes("recoverInterruptedBoot()") && source.includes(`.milliseconds(${timeout})`) && source.includes("quarantinedReleases") && nativeConfig.includes("AbsoluteMobileUpdateWatchdogPlugin") ? pass("ios.update-watchdog", `The native update boot watchdog is projected with a ${timeout}ms deadline.`, path) : fail("ios.update-watchdog", "The iOS update boot watchdog does not match mobile config.", path, "Run `absolute mobile sync ios` before building the release.");
|
|
19919
20147
|
}
|
|
19920
|
-
const sourceRoot =
|
|
20148
|
+
const sourceRoot = join8(config.nativeProjectDirectory, "android/app/src/main");
|
|
19921
20149
|
const sources = await sourceFiles(sourceRoot, new Set([".java", ".kt"]));
|
|
19922
20150
|
const [activity, plugin] = await Promise.all([
|
|
19923
20151
|
containsPattern(sources, /recoverInterruptedBoot\s*\(\s*this\s*\)/u),
|
|
@@ -19930,27 +20158,27 @@ var nativeObservabilityProjectionCheck = async (config, platform) => {
|
|
|
19930
20158
|
return;
|
|
19931
20159
|
const nativeRoot = config.nativeProjectDirectory;
|
|
19932
20160
|
if (config.engine === "expo") {
|
|
19933
|
-
const moduleRoot =
|
|
19934
|
-
const configPath =
|
|
19935
|
-
const sourcePath = platform === "android" ?
|
|
20161
|
+
const moduleRoot = join8(nativeRoot, "modules", "absolute-mobile-observability");
|
|
20162
|
+
const configPath = join8(moduleRoot, "expo-module.config.json");
|
|
20163
|
+
const sourcePath = platform === "android" ? join8(moduleRoot, "android/src/main/java/expo/modules/absolutemobileobservability/AbsoluteMobileObservabilityModule.kt") : join8(moduleRoot, "ios/AbsoluteMobileObservabilityModule.swift");
|
|
19936
20164
|
const [moduleConfig, source] = await Promise.all([
|
|
19937
|
-
|
|
19938
|
-
|
|
20165
|
+
readFile9(configPath, "utf8").catch(() => ""),
|
|
20166
|
+
readFile9(sourcePath, "utf8").catch(() => "")
|
|
19939
20167
|
]);
|
|
19940
20168
|
const valid = moduleConfig.includes("AbsoluteMobileObservabilityModule") && source.includes("AbsoluteMobileObservability") && (platform === "android" ? source.includes("getHistoricalProcessExitReasons") : source.includes("MXMetricManagerSubscriber"));
|
|
19941
20169
|
return valid ? pass(`${platform}.native-observability`, `The Expo ${platform} native process collector is projected and autolinkable.`, sourcePath) : fail(`${platform}.native-observability`, `The Expo ${platform} native process collector is missing or incomplete.`, sourcePath, `Run \`absolute mobile build ${platform}\` before building the release.`);
|
|
19942
20170
|
}
|
|
19943
20171
|
if (platform === "ios") {
|
|
19944
|
-
const delegatePath =
|
|
19945
|
-
const configPath =
|
|
20172
|
+
const delegatePath = join8(nativeRoot, "ios/App/App/AppDelegate.swift");
|
|
20173
|
+
const configPath = join8(nativeRoot, "ios/App/App/capacitor.config.json");
|
|
19946
20174
|
const [delegate, nativeConfig] = await Promise.all([
|
|
19947
|
-
|
|
19948
|
-
|
|
20175
|
+
readFile9(delegatePath, "utf8").catch(() => ""),
|
|
20176
|
+
readFile9(configPath, "utf8").catch(() => "")
|
|
19949
20177
|
]);
|
|
19950
20178
|
const valid = delegate.includes("AbsoluteMobileObservabilityPlugin") && delegate.includes("MXMetricManagerSubscriber") && nativeConfig.includes("AbsoluteMobileObservabilityPlugin");
|
|
19951
20179
|
return valid ? pass("ios.native-observability", "The iOS MetricKit native process collector is projected and registered.", delegatePath) : fail("ios.native-observability", "The iOS MetricKit native process collector is missing or unregistered.", delegatePath, "Run `absolute mobile sync ios` before building the release.");
|
|
19952
20180
|
}
|
|
19953
|
-
const sourceRoot =
|
|
20181
|
+
const sourceRoot = join8(nativeRoot, "android/app/src/main");
|
|
19954
20182
|
const sources = await sourceFiles(sourceRoot, new Set([".java", ".kt"]));
|
|
19955
20183
|
const [registration, plugin] = await Promise.all([
|
|
19956
20184
|
containsPattern(sources, /registerPlugin\s*\(\s*AbsoluteMobileObservabilityPlugin(?:::class\.java|\.class)\s*\)/u),
|
|
@@ -19971,14 +20199,14 @@ var iosNativeSecurityCheck = async (iosRoot) => {
|
|
|
19971
20199
|
return pass("ios.native-debugging", "iOS source does not enable release debugger attachment or WebView inspection.", entitlementPaths[0] ?? iosRoot);
|
|
19972
20200
|
};
|
|
19973
20201
|
var iosDeepLinkProjectionCheck = async (config, iosRoot) => {
|
|
19974
|
-
const infoPath =
|
|
19975
|
-
const entitlementsPath =
|
|
19976
|
-
const projectPath2 =
|
|
20202
|
+
const infoPath = join8(iosRoot, "App/App/Info.plist");
|
|
20203
|
+
const entitlementsPath = join8(iosRoot, "App/AbsoluteJS.entitlements");
|
|
20204
|
+
const projectPath2 = join8(iosRoot, "App/App.xcodeproj/project.pbxproj");
|
|
19977
20205
|
try {
|
|
19978
20206
|
const [info, entitlements, project] = await Promise.all([
|
|
19979
|
-
|
|
19980
|
-
|
|
19981
|
-
|
|
20207
|
+
readFile9(infoPath, "utf8"),
|
|
20208
|
+
readFile9(entitlementsPath, "utf8"),
|
|
20209
|
+
readFile9(projectPath2, "utf8")
|
|
19982
20210
|
]);
|
|
19983
20211
|
if (config.deepLinkScheme && (!info.includes("<key>CFBundleURLTypes</key>") || !info.includes(`<string>${config.deepLinkScheme}</string>`)))
|
|
19984
20212
|
throw new TypeError("iOS custom URL scheme does not match mobile config.");
|
|
@@ -19994,7 +20222,7 @@ var iosDeepLinkProjectionCheck = async (config, iosRoot) => {
|
|
|
19994
20222
|
var syncSchemaReleaseCheck = (projectRoot) => {
|
|
19995
20223
|
if (!projectUsesAbsoluteSync(projectRoot))
|
|
19996
20224
|
return;
|
|
19997
|
-
const manifestPath =
|
|
20225
|
+
const manifestPath = join8(projectRoot, "package.json");
|
|
19998
20226
|
try {
|
|
19999
20227
|
const schema = discoverAbsoluteSyncSchema(projectRoot);
|
|
20000
20228
|
const versions = schema.components.map((component) => `${component.id}@${component.version}`).join(", ");
|
|
@@ -20024,8 +20252,8 @@ var IOS_USAGE_KEYS = {
|
|
|
20024
20252
|
var androidDevicePermissionCheck = async (config, permissions) => {
|
|
20025
20253
|
if (!config.platforms.includes("android") || permissions.length === 0)
|
|
20026
20254
|
return;
|
|
20027
|
-
const path =
|
|
20028
|
-
const source = await
|
|
20255
|
+
const path = join8(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
20256
|
+
const source = await readFile9(path, "utf8");
|
|
20029
20257
|
const missing = permissions.filter((permission) => !source.includes(`android:name="${permission}"`) && !source.includes(`android:name='${permission}'`));
|
|
20030
20258
|
if (missing.length === 0)
|
|
20031
20259
|
return;
|
|
@@ -20034,9 +20262,9 @@ var androidDevicePermissionCheck = async (config, permissions) => {
|
|
|
20034
20262
|
var iosDevicePermissionCheck = async (config, purposes) => {
|
|
20035
20263
|
if (!config.platforms.includes("ios") || purposes.length === 0)
|
|
20036
20264
|
return;
|
|
20037
|
-
const iosRoot =
|
|
20038
|
-
const path = config.engine === "expo" ? await uniqueExpoIosFile(iosRoot, "**/Info.plist", "Info.plist") :
|
|
20039
|
-
const source = await
|
|
20265
|
+
const iosRoot = join8(config.nativeProjectDirectory, "ios");
|
|
20266
|
+
const path = config.engine === "expo" ? await uniqueExpoIosFile(iosRoot, "**/Info.plist", "Info.plist") : join8(iosRoot, "App/App/Info.plist");
|
|
20267
|
+
const source = await readFile9(path, "utf8");
|
|
20040
20268
|
const missing = purposes.filter((purpose) => !source.includes(`<key>${IOS_USAGE_KEYS[purpose]}</key>`));
|
|
20041
20269
|
if (missing.length === 0)
|
|
20042
20270
|
return;
|
|
@@ -20046,7 +20274,7 @@ var expoIosPrivacyCapabilityCheck = async (iosRoot, project, requirements) => {
|
|
|
20046
20274
|
if (requirements.iosPrivacyAccessedApis.length === 0)
|
|
20047
20275
|
return;
|
|
20048
20276
|
const privacyPath = await uniqueExpoIosFile(iosRoot, "**/PrivacyInfo.xcprivacy", "privacy manifest");
|
|
20049
|
-
const privacy = await
|
|
20277
|
+
const privacy = await readFile9(privacyPath, "utf8");
|
|
20050
20278
|
const missing = requirements.iosPrivacyAccessedApis.some(({ api, reasons }) => !privacy.includes(`<string>${api}</string>`) || reasons.some((reason) => !privacy.includes(`<string>${reason}</string>`)));
|
|
20051
20279
|
if (!missing && project.includes(privacyPath.split(/[\\/]/u).at(-1) ?? ""))
|
|
20052
20280
|
return;
|
|
@@ -20056,15 +20284,15 @@ var expoIosPushCapabilityCheck = async (iosRoot, requirements) => {
|
|
|
20056
20284
|
if (!requirements.iosPushNotifications)
|
|
20057
20285
|
return;
|
|
20058
20286
|
const entitlementsPath = await uniqueExpoIosFile(iosRoot, "**/*.entitlements", "entitlements");
|
|
20059
|
-
const entitlements = await
|
|
20287
|
+
const entitlements = await readFile9(entitlementsPath, "utf8");
|
|
20060
20288
|
if (entitlements.includes("<key>aps-environment</key>"))
|
|
20061
20289
|
return;
|
|
20062
20290
|
return fail("mobile.device-capabilities", "Expo iOS push entitlement does not match detected capabilities.", entitlementsPath, "Run `absolute mobile build ios` to regenerate native push integration.");
|
|
20063
20291
|
};
|
|
20064
20292
|
var expoIosCapabilityProjectionCheck = async (config, requirements) => {
|
|
20065
|
-
const iosRoot =
|
|
20293
|
+
const iosRoot = join8(config.nativeProjectDirectory, "ios");
|
|
20066
20294
|
const projectPath2 = await uniqueExpoIosFile(iosRoot, "**/*.xcodeproj/project.pbxproj", "Xcode project");
|
|
20067
|
-
const project = await
|
|
20295
|
+
const project = await readFile9(projectPath2, "utf8");
|
|
20068
20296
|
const privacy = await expoIosPrivacyCapabilityCheck(iosRoot, project, requirements);
|
|
20069
20297
|
if (privacy)
|
|
20070
20298
|
return privacy;
|
|
@@ -20075,28 +20303,28 @@ var iosCapabilityProjectionCheck = async (config, requirements) => {
|
|
|
20075
20303
|
return;
|
|
20076
20304
|
if (config.engine === "expo")
|
|
20077
20305
|
return expoIosCapabilityProjectionCheck(config, requirements);
|
|
20078
|
-
const appRoot =
|
|
20079
|
-
const infoPath =
|
|
20080
|
-
const info = await
|
|
20306
|
+
const appRoot = join8(config.nativeProjectDirectory, "ios/App/App");
|
|
20307
|
+
const infoPath = join8(appRoot, "Info.plist");
|
|
20308
|
+
const info = await readFile9(infoPath, "utf8").catch(() => "");
|
|
20081
20309
|
if (requirements.iosSystemBars && !/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<true\s*\/>/u.test(info))
|
|
20082
20310
|
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.");
|
|
20083
20311
|
if (requirements.iosPrivacyAccessedApis.length > 0) {
|
|
20084
|
-
const privacyPath =
|
|
20085
|
-
const projectPath2 =
|
|
20312
|
+
const privacyPath = join8(appRoot, "PrivacyInfo.xcprivacy");
|
|
20313
|
+
const projectPath2 = join8(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
20086
20314
|
const [privacy, project] = await Promise.all([
|
|
20087
|
-
|
|
20088
|
-
|
|
20315
|
+
readFile9(privacyPath, "utf8").catch(() => ""),
|
|
20316
|
+
readFile9(projectPath2, "utf8").catch(() => "")
|
|
20089
20317
|
]);
|
|
20090
20318
|
const missing = requirements.iosPrivacyAccessedApis.some(({ api, reasons }) => !privacy.includes(`<string>${api}</string>`) || reasons.some((reason) => !privacy.includes(`<string>${reason}</string>`)));
|
|
20091
20319
|
if (missing || !project.includes("PrivacyInfo.xcprivacy in Resources"))
|
|
20092
20320
|
return fail("mobile.device-capabilities", "iOS privacy manifest or target membership does not match detected native capabilities.", privacyPath, "Run `absolute mobile sync ios` to regenerate and target PrivacyInfo.xcprivacy.");
|
|
20093
20321
|
}
|
|
20094
20322
|
if (requirements.iosPushNotifications) {
|
|
20095
|
-
const entitlementsPath =
|
|
20096
|
-
const delegatePath =
|
|
20323
|
+
const entitlementsPath = join8(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
20324
|
+
const delegatePath = join8(appRoot, "AppDelegate.swift");
|
|
20097
20325
|
const [entitlements, delegate] = await Promise.all([
|
|
20098
|
-
|
|
20099
|
-
|
|
20326
|
+
readFile9(entitlementsPath, "utf8").catch(() => ""),
|
|
20327
|
+
readFile9(delegatePath, "utf8").catch(() => "")
|
|
20100
20328
|
]);
|
|
20101
20329
|
if (!entitlements.includes("<key>aps-environment</key>") || !delegate.includes("capacitorDidRegisterForRemoteNotifications") || !delegate.includes("capacitorDidFailToRegisterForRemoteNotifications"))
|
|
20102
20330
|
return fail("mobile.device-capabilities", "iOS push entitlement or AppDelegate forwarding does not match detected capabilities.", entitlementsPath, "Run `absolute mobile sync ios` to regenerate native push integration.");
|
|
@@ -20104,13 +20332,13 @@ var iosCapabilityProjectionCheck = async (config, requirements) => {
|
|
|
20104
20332
|
return;
|
|
20105
20333
|
};
|
|
20106
20334
|
var deviceCapabilityReleaseCheck = async (config, projectRoot) => {
|
|
20107
|
-
const manifestPath =
|
|
20335
|
+
const manifestPath = join8(projectRoot, "package.json");
|
|
20108
20336
|
try {
|
|
20109
20337
|
const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot, config.engine);
|
|
20110
20338
|
const assertPackages = async () => {
|
|
20111
20339
|
if (config.engine === "capacitor")
|
|
20112
20340
|
return assertAbsoluteDeviceCapabilityPackages(projectRoot, plan);
|
|
20113
|
-
const generated = await readJsonObject(
|
|
20341
|
+
const generated = await readJsonObject(join8(config.nativeProjectDirectory, "package.json"));
|
|
20114
20342
|
const declarations = packageDeclarations(generated);
|
|
20115
20343
|
const missing = plan.requiredPackages.filter((spec) => {
|
|
20116
20344
|
const separator = spec.lastIndexOf("@");
|
|
@@ -20137,11 +20365,11 @@ var deviceCapabilityReleaseCheck = async (config, projectRoot) => {
|
|
|
20137
20365
|
}
|
|
20138
20366
|
};
|
|
20139
20367
|
var inspectAndroidRelease = async (config, projectRoot) => {
|
|
20140
|
-
const androidRoot =
|
|
20141
|
-
const nativeConfigPath =
|
|
20142
|
-
const manifestPath =
|
|
20143
|
-
const publicRoot =
|
|
20144
|
-
const journalPath =
|
|
20368
|
+
const androidRoot = join8(config.nativeProjectDirectory, "android");
|
|
20369
|
+
const nativeConfigPath = join8(androidRoot, "app", "src", "main", "assets", "capacitor.config.json");
|
|
20370
|
+
const manifestPath = join8(androidRoot, "app", "src", "main", "AndroidManifest.xml");
|
|
20371
|
+
const publicRoot = join8(androidRoot, "app", "src", "main", "assets", "public");
|
|
20372
|
+
const journalPath = join8(projectRoot, ".absolutejs", "mobile", "dev-session", "journal.json");
|
|
20145
20373
|
const checks = await Promise.all([
|
|
20146
20374
|
journalReleaseCheck(journalPath, "android"),
|
|
20147
20375
|
capacitorConfigReleaseCheck(nativeConfigPath),
|
|
@@ -20162,9 +20390,9 @@ var inspectAndroidRelease = async (config, projectRoot) => {
|
|
|
20162
20390
|
}));
|
|
20163
20391
|
};
|
|
20164
20392
|
var inspectExpoAndroidRelease = async (config, projectRoot) => {
|
|
20165
|
-
const androidRoot =
|
|
20166
|
-
const manifestPath =
|
|
20167
|
-
const journalPath =
|
|
20393
|
+
const androidRoot = join8(config.nativeProjectDirectory, "android");
|
|
20394
|
+
const manifestPath = join8(androidRoot, "app", "src", "main", "AndroidManifest.xml");
|
|
20395
|
+
const journalPath = join8(projectRoot, ".absolutejs", "mobile", "expo-dev-session", "journal.json");
|
|
20168
20396
|
const checks = await Promise.all([
|
|
20169
20397
|
journalReleaseCheck(journalPath, "android"),
|
|
20170
20398
|
expoApplicationConfigCheck(config, projectRoot),
|
|
@@ -20190,7 +20418,7 @@ var uniqueExpoIosFile = async (iosRoot, pattern, label) => {
|
|
|
20190
20418
|
const [path] = paths;
|
|
20191
20419
|
if (!path)
|
|
20192
20420
|
throw new TypeError(`Generated Expo iOS project is missing its ${label}.`);
|
|
20193
|
-
return
|
|
20421
|
+
return join8(iosRoot, path);
|
|
20194
20422
|
};
|
|
20195
20423
|
var expoIosNativeProjectionCheck = async (config, iosRoot) => {
|
|
20196
20424
|
try {
|
|
@@ -20200,9 +20428,9 @@ var expoIosNativeProjectionCheck = async (config, iosRoot) => {
|
|
|
20200
20428
|
uniqueExpoIosFile(iosRoot, "**/*.xcodeproj/project.pbxproj", "Xcode project")
|
|
20201
20429
|
]);
|
|
20202
20430
|
const [info, entitlements, project] = await Promise.all([
|
|
20203
|
-
|
|
20204
|
-
|
|
20205
|
-
|
|
20431
|
+
readFile9(infoPath, "utf8"),
|
|
20432
|
+
readFile9(entitlementsPath, "utf8"),
|
|
20433
|
+
readFile9(projectPath2, "utf8")
|
|
20206
20434
|
]);
|
|
20207
20435
|
if (/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(info))
|
|
20208
20436
|
throw new TypeError("Expo iOS App Transport Security permits arbitrary network loads.");
|
|
@@ -20218,8 +20446,8 @@ var expoIosNativeProjectionCheck = async (config, iosRoot) => {
|
|
|
20218
20446
|
}
|
|
20219
20447
|
};
|
|
20220
20448
|
var inspectExpoIosRelease = async (config, projectRoot) => {
|
|
20221
|
-
const iosRoot =
|
|
20222
|
-
const journalPath =
|
|
20449
|
+
const iosRoot = join8(config.nativeProjectDirectory, "ios");
|
|
20450
|
+
const journalPath = join8(projectRoot, ".absolutejs", "mobile", "expo-dev-session", "journal.json");
|
|
20223
20451
|
const checks = [
|
|
20224
20452
|
await journalReleaseCheck(journalPath, "ios")
|
|
20225
20453
|
];
|
|
@@ -20242,11 +20470,11 @@ var inspectExpoIosRelease = async (config, projectRoot) => {
|
|
|
20242
20470
|
}));
|
|
20243
20471
|
};
|
|
20244
20472
|
var inspectIosRelease = async (config, projectRoot) => {
|
|
20245
|
-
const iosAppRoot =
|
|
20246
|
-
const nativeConfigPath =
|
|
20247
|
-
const infoPath =
|
|
20248
|
-
const publicRoot =
|
|
20249
|
-
const journalPath =
|
|
20473
|
+
const iosAppRoot = join8(config.nativeProjectDirectory, "ios", "App", "App");
|
|
20474
|
+
const nativeConfigPath = join8(iosAppRoot, "capacitor.config.json");
|
|
20475
|
+
const infoPath = join8(iosAppRoot, "Info.plist");
|
|
20476
|
+
const publicRoot = join8(iosAppRoot, "public");
|
|
20477
|
+
const journalPath = join8(projectRoot, ".absolutejs", "mobile", "ios-dev-session", "journal.json");
|
|
20250
20478
|
const checks = [
|
|
20251
20479
|
await journalReleaseCheck(journalPath, "ios")
|
|
20252
20480
|
];
|
|
@@ -20258,7 +20486,7 @@ var inspectIosRelease = async (config, projectRoot) => {
|
|
|
20258
20486
|
}
|
|
20259
20487
|
if (!await pathExists2(nativeConfigPath)) {
|
|
20260
20488
|
checks.push(fail("ios.capacitor-config", "The generated iOS Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync ios` before release validation."));
|
|
20261
|
-
} else if (isUnsafeCapacitorConfig(await
|
|
20489
|
+
} else if (isUnsafeCapacitorConfig(await readFile9(nativeConfigPath, "utf8"))) {
|
|
20262
20490
|
checks.push(fail("ios.capacitor-config", "iOS Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync ios`; do not ship development transport overrides."));
|
|
20263
20491
|
} else {
|
|
20264
20492
|
checks.push(pass("ios.capacitor-config", "iOS Capacitor config contains no development transport overrides.", nativeConfigPath));
|
|
@@ -20267,12 +20495,12 @@ var inspectIosRelease = async (config, projectRoot) => {
|
|
|
20267
20495
|
if (!await pathExists2(infoPath)) {
|
|
20268
20496
|
checks.push(fail("ios.transport-security", "The iOS Info.plist is missing.", infoPath, "Run `absolute mobile sync ios` before release validation."));
|
|
20269
20497
|
} else {
|
|
20270
|
-
const info = await
|
|
20498
|
+
const info = await readFile9(infoPath, "utf8");
|
|
20271
20499
|
checks.push(/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(info) ? fail("ios.transport-security", "iOS App Transport Security permits arbitrary network loads.", infoPath, "Remove NSAllowsArbitraryLoads from the release Info.plist.") : pass("ios.transport-security", "iOS App Transport Security does not permit arbitrary loads.", infoPath));
|
|
20272
20500
|
}
|
|
20273
20501
|
const hmrAsset = await findHmrAsset(publicRoot);
|
|
20274
20502
|
checks.push(hmrAsset ? fail("ios.hmr-assets", "A packaged iOS asset contains the development HMR client.", hmrAsset, "Rebuild the production mobile bundle and run Capacitor sync again.") : pass("ios.hmr-assets", "Packaged iOS assets contain no development HMR markers.", publicRoot));
|
|
20275
|
-
checks.push(await embeddedBundleReleaseCheck(config, projectRoot, "ios", publicRoot), await contentSecurityPolicyCheck(config, "ios", publicRoot), await iosNativeSecurityCheck(
|
|
20503
|
+
checks.push(await embeddedBundleReleaseCheck(config, projectRoot, "ios", publicRoot), await contentSecurityPolicyCheck(config, "ios", publicRoot), await iosNativeSecurityCheck(join8(config.nativeProjectDirectory, "ios")), await iosDeepLinkProjectionCheck(config, join8(config.nativeProjectDirectory, "ios")));
|
|
20276
20504
|
const nativeObservability = await nativeObservabilityProjectionCheck(config, "ios");
|
|
20277
20505
|
if (nativeObservability)
|
|
20278
20506
|
checks.push(nativeObservability);
|
|
@@ -20342,16 +20570,16 @@ import { createHash as createHash2 } from "crypto";
|
|
|
20342
20570
|
import {
|
|
20343
20571
|
access as access5,
|
|
20344
20572
|
copyFile,
|
|
20345
|
-
mkdir as
|
|
20573
|
+
mkdir as mkdir7,
|
|
20346
20574
|
mkdtemp,
|
|
20347
|
-
readFile as
|
|
20575
|
+
readFile as readFile10,
|
|
20348
20576
|
realpath,
|
|
20349
|
-
rename as
|
|
20577
|
+
rename as rename8,
|
|
20350
20578
|
rm as rm4,
|
|
20351
20579
|
stat as stat2,
|
|
20352
|
-
writeFile as
|
|
20580
|
+
writeFile as writeFile9
|
|
20353
20581
|
} from "fs/promises";
|
|
20354
|
-
import { dirname as
|
|
20582
|
+
import { dirname as dirname7, isAbsolute as isAbsolute2, join as join9, relative as relative4, resolve as resolve5, sep as sep2 } from "path";
|
|
20355
20583
|
var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1;
|
|
20356
20584
|
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20357
20585
|
var requireManifest = (value) => {
|
|
@@ -20430,7 +20658,7 @@ var windowsJarsignerPath = (path, capture) => {
|
|
|
20430
20658
|
}
|
|
20431
20659
|
return translated;
|
|
20432
20660
|
};
|
|
20433
|
-
var sha256File = async (path) => createHash2("sha256").update(await
|
|
20661
|
+
var sha256File = async (path) => createHash2("sha256").update(await readFile10(path)).digest("hex");
|
|
20434
20662
|
var fingerprintExpoAndroidProject = async (nativeDirectory) => {
|
|
20435
20663
|
const root = await realpath(nativeDirectory);
|
|
20436
20664
|
const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
|
|
@@ -20438,7 +20666,7 @@ var fingerprintExpoAndroidProject = async (nativeDirectory) => {
|
|
|
20438
20666
|
const parts = path.replaceAll("\\", "/").split("/");
|
|
20439
20667
|
return !parts.includes(".gradle") && !parts.includes("build");
|
|
20440
20668
|
}).sort().map(async (path) => {
|
|
20441
|
-
const contents = await
|
|
20669
|
+
const contents = await readFile10(join9(root, path));
|
|
20442
20670
|
return `${path.replaceAll("\\", "/")}\x00${createHash2("sha256").update(contents).digest("hex")}\x00`;
|
|
20443
20671
|
}));
|
|
20444
20672
|
return createHash2("sha256").update(records.join("")).digest("hex");
|
|
@@ -20453,11 +20681,11 @@ var safeOutputDirectory = (projectRoot, requested) => {
|
|
|
20453
20681
|
return output;
|
|
20454
20682
|
};
|
|
20455
20683
|
var installRelease = async (artifactPath, metadata, outputRoot) => {
|
|
20456
|
-
const releaseRoot =
|
|
20684
|
+
const releaseRoot = join9(outputRoot, metadata.releaseId);
|
|
20457
20685
|
const artifactName = "app-release.aab";
|
|
20458
|
-
const destination =
|
|
20686
|
+
const destination = join9(releaseRoot, artifactName);
|
|
20459
20687
|
if (await pathExists3(releaseRoot)) {
|
|
20460
|
-
const existing = requireManifestIdentity(JSON.parse(await
|
|
20688
|
+
const existing = requireManifestIdentity(JSON.parse(await readFile10(join9(releaseRoot, "release.json"), "utf8")), metadata);
|
|
20461
20689
|
const [installedBytes, installedSha256] = await Promise.all([
|
|
20462
20690
|
stat2(destination).then(({ size }) => size),
|
|
20463
20691
|
sha256File(destination)
|
|
@@ -20467,17 +20695,17 @@ var installRelease = async (artifactPath, metadata, outputRoot) => {
|
|
|
20467
20695
|
}
|
|
20468
20696
|
return { artifactPath: destination, metadata: existing, releaseRoot };
|
|
20469
20697
|
}
|
|
20470
|
-
await
|
|
20471
|
-
const staging = await mkdtemp(
|
|
20698
|
+
await mkdir7(dirname7(releaseRoot), { recursive: true });
|
|
20699
|
+
const staging = await mkdtemp(join9(dirname7(releaseRoot), ".android-stage-"));
|
|
20472
20700
|
try {
|
|
20473
|
-
await copyFile(artifactPath,
|
|
20701
|
+
await copyFile(artifactPath, join9(staging, artifactName));
|
|
20474
20702
|
const complete = {
|
|
20475
20703
|
...metadata,
|
|
20476
20704
|
artifact: artifactName
|
|
20477
20705
|
};
|
|
20478
|
-
await
|
|
20706
|
+
await writeFile9(join9(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
|
|
20479
20707
|
`, { flag: "wx" });
|
|
20480
|
-
await
|
|
20708
|
+
await rename8(staging, releaseRoot);
|
|
20481
20709
|
return { artifactPath: destination, metadata: complete, releaseRoot };
|
|
20482
20710
|
} finally {
|
|
20483
20711
|
await rm4(staging, { force: true, recursive: true }).catch(() => {
|
|
@@ -20505,8 +20733,8 @@ var buildAbsoluteAndroidRelease = async (options) => {
|
|
|
20505
20733
|
const projectRoot = resolve5(options.projectRoot);
|
|
20506
20734
|
const host = options.host ?? detectAbsoluteMobileHost();
|
|
20507
20735
|
const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
|
|
20508
|
-
const nativeDirectory =
|
|
20509
|
-
const manifest = requireManifest(JSON.parse(await
|
|
20736
|
+
const nativeDirectory = join9(options.config.nativeProjectDirectory, "android");
|
|
20737
|
+
const manifest = requireManifest(JSON.parse(await readFile10(join9(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
|
|
20510
20738
|
if (manifest.appId !== options.config.appId) {
|
|
20511
20739
|
throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
|
|
20512
20740
|
}
|
|
@@ -20595,20 +20823,20 @@ var buildAbsoluteAndroidRelease = async (options) => {
|
|
|
20595
20823
|
import { createHash as createHash3 } from "crypto";
|
|
20596
20824
|
import {
|
|
20597
20825
|
access as access6,
|
|
20598
|
-
mkdir as
|
|
20826
|
+
mkdir as mkdir8,
|
|
20599
20827
|
mkdtemp as mkdtemp2,
|
|
20600
|
-
readFile as
|
|
20601
|
-
rename as
|
|
20828
|
+
readFile as readFile11,
|
|
20829
|
+
rename as rename9,
|
|
20602
20830
|
rm as rm5,
|
|
20603
20831
|
stat as stat3,
|
|
20604
|
-
writeFile as
|
|
20832
|
+
writeFile as writeFile10
|
|
20605
20833
|
} from "fs/promises";
|
|
20606
20834
|
import { homedir } from "os";
|
|
20607
20835
|
import {
|
|
20608
20836
|
basename,
|
|
20609
|
-
dirname as
|
|
20837
|
+
dirname as dirname8,
|
|
20610
20838
|
isAbsolute as isAbsolute3,
|
|
20611
|
-
join as
|
|
20839
|
+
join as join10,
|
|
20612
20840
|
relative as relative5,
|
|
20613
20841
|
resolve as resolve6,
|
|
20614
20842
|
sep as sep3
|
|
@@ -20662,7 +20890,10 @@ var parseAbsoluteAndroidInstalledApp = (appId, output) => {
|
|
|
20662
20890
|
var ABSOLUTE_BUNDLETOOL_VERSION = "1.18.3";
|
|
20663
20891
|
var ABSOLUTE_BUNDLETOOL_SHA256 = "a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29";
|
|
20664
20892
|
var ABSOLUTE_BUNDLETOOL_URL = `https://github.com/google/bundletool/releases/download/${ABSOLUTE_BUNDLETOOL_VERSION}/bundletool-all-${ABSOLUTE_BUNDLETOOL_VERSION}.jar`;
|
|
20665
|
-
var
|
|
20893
|
+
var EMBEDDED_READY_MARKERS = {
|
|
20894
|
+
capacitor: "Capacitor embedded web content ready",
|
|
20895
|
+
expo: "Expo embedded web content ready"
|
|
20896
|
+
};
|
|
20666
20897
|
var RELEASE_READY_POLL_MS = 250;
|
|
20667
20898
|
var RELEASE_READY_TIMEOUT_MS = 30000;
|
|
20668
20899
|
var RELEASE_STABILITY_MS = 2000;
|
|
@@ -20675,7 +20906,7 @@ var exists2 = async (path) => {
|
|
|
20675
20906
|
return false;
|
|
20676
20907
|
}
|
|
20677
20908
|
};
|
|
20678
|
-
var sha256 = async (path) => createHash3("sha256").update(await
|
|
20909
|
+
var sha256 = async (path) => createHash3("sha256").update(await readFile11(path)).digest("hex");
|
|
20679
20910
|
var inside = (root, candidate) => {
|
|
20680
20911
|
const path = relative5(resolve6(root), resolve6(candidate));
|
|
20681
20912
|
return path === "" || !isAbsolute3(path) && path !== ".." && !path.startsWith(`..${sep3}`);
|
|
@@ -20717,7 +20948,7 @@ var parseMetadata = (value) => {
|
|
|
20717
20948
|
throw new TypeError("Android release ID does not match its artifact digest.");
|
|
20718
20949
|
return metadata;
|
|
20719
20950
|
};
|
|
20720
|
-
var absoluteBundletoolPath = (root = process.env.ABSOLUTE_BUNDLETOOL_ROOT ??
|
|
20951
|
+
var absoluteBundletoolPath = (root = process.env.ABSOLUTE_BUNDLETOOL_ROOT ?? join10(homedir(), ".absolutejs", "tools", "bundletool")) => join10(root, ABSOLUTE_BUNDLETOOL_VERSION, "bundletool.jar");
|
|
20721
20952
|
var inspectAbsoluteBundletool = async (root) => {
|
|
20722
20953
|
const path = absoluteBundletoolPath(root);
|
|
20723
20954
|
if (!await exists2(path))
|
|
@@ -20731,9 +20962,9 @@ var readAbsoluteAndroidRelease = async (projectRoot, requested) => {
|
|
|
20731
20962
|
const selected = resolve6(root, requested);
|
|
20732
20963
|
if (!inside(root, selected))
|
|
20733
20964
|
throw new TypeError("mobile test android --release must remain inside the project.");
|
|
20734
|
-
const metadataPath = basename(selected) === "release.json" ? selected :
|
|
20735
|
-
const releaseRoot =
|
|
20736
|
-
const metadata = parseMetadata(JSON.parse(await
|
|
20965
|
+
const metadataPath = basename(selected) === "release.json" ? selected : join10(selected, "release.json");
|
|
20966
|
+
const releaseRoot = dirname8(metadataPath);
|
|
20967
|
+
const metadata = parseMetadata(JSON.parse(await readFile11(metadataPath, "utf8")));
|
|
20737
20968
|
const artifactPath = resolve6(releaseRoot, metadata.artifact);
|
|
20738
20969
|
if (!inside(releaseRoot, artifactPath) || basename(artifactPath) !== metadata.artifact)
|
|
20739
20970
|
throw new TypeError("Android release artifact path is invalid.");
|
|
@@ -20761,12 +20992,12 @@ var ensureAbsoluteBundletool = async (options) => {
|
|
|
20761
20992
|
const digest = createHash3("sha256").update(bytes).digest("hex");
|
|
20762
20993
|
if (digest !== ABSOLUTE_BUNDLETOOL_SHA256)
|
|
20763
20994
|
throw new TypeError("Downloaded Bundletool failed checksum verification.");
|
|
20764
|
-
await
|
|
20765
|
-
const staging = await mkdtemp2(
|
|
20766
|
-
const candidate =
|
|
20995
|
+
await mkdir8(dirname8(inspected.path), { recursive: true });
|
|
20996
|
+
const staging = await mkdtemp2(join10(dirname8(inspected.path), ".install-"));
|
|
20997
|
+
const candidate = join10(staging, "bundletool.jar");
|
|
20767
20998
|
try {
|
|
20768
|
-
await
|
|
20769
|
-
await
|
|
20999
|
+
await writeFile10(candidate, bytes, { flag: "wx" });
|
|
21000
|
+
await rename9(candidate, inspected.path).catch(async (error) => {
|
|
20770
21001
|
if (await exists2(inspected.path))
|
|
20771
21002
|
return;
|
|
20772
21003
|
throw error;
|
|
@@ -20803,7 +21034,7 @@ var adb = (path, serial, ...args) => [
|
|
|
20803
21034
|
serial,
|
|
20804
21035
|
...args
|
|
20805
21036
|
];
|
|
20806
|
-
var waitForMarker = async (run, adbPath, serial, appId, timeoutMs) => {
|
|
21037
|
+
var waitForMarker = async (run, adbPath, serial, appId, engine, timeoutMs) => {
|
|
20807
21038
|
const startedAt = performance.now();
|
|
20808
21039
|
const inspect = async () => {
|
|
20809
21040
|
if (performance.now() - startedAt >= timeoutMs)
|
|
@@ -20814,9 +21045,9 @@ var waitForMarker = async (run, adbPath, serial, appId, timeoutMs) => {
|
|
|
20814
21045
|
]);
|
|
20815
21046
|
if (process2.exitCode !== 0 || !process2.stdout.trim())
|
|
20816
21047
|
throw new Error("The installed Android release exited before becoming ready.");
|
|
20817
|
-
if (logs.stdout.includes(
|
|
21048
|
+
if (logs.stdout.includes(EMBEDDED_READY_MARKERS[engine]))
|
|
20818
21049
|
return;
|
|
20819
|
-
const failedPhase = /Expo embedded web phase: (assets-(?:root|directory|destination|module|source|download|copy|read|write|finalize|unexpected)|devices)-failed/u.exec(logs.stdout)?.[1];
|
|
21050
|
+
const failedPhase = engine === "expo" ? /Expo embedded web phase: (assets-(?:root|directory|destination|module|source|download|copy|read|write|finalize|unexpected)|devices)-failed/u.exec(logs.stdout)?.[1] : undefined;
|
|
20820
21051
|
if (failedPhase)
|
|
20821
21052
|
throw new Error(`The installed release could not initialize its embedded ${failedPhase} runtime offline.`);
|
|
20822
21053
|
await Bun.sleep(RELEASE_READY_POLL_MS);
|
|
@@ -20834,14 +21065,12 @@ var launch = async (run, adbPath, serial, appId, launchUrl) => {
|
|
|
20834
21065
|
return Math.round(performance.now() - startedAt);
|
|
20835
21066
|
};
|
|
20836
21067
|
var runAbsoluteAndroidReleaseAcceptance = async (options) => {
|
|
20837
|
-
if (options.release.metadata.engine !== "expo")
|
|
20838
|
-
throw new TypeError("Installed AAB release acceptance currently requires the Expo engine.");
|
|
20839
21068
|
if (!options.serial.startsWith("emulator-"))
|
|
20840
21069
|
throw new TypeError("Installed release acceptance changes network state and therefore requires an Android emulator.");
|
|
20841
21070
|
const run = options.run ?? defaultRun;
|
|
20842
21071
|
const startedAt = performance.now();
|
|
20843
|
-
await
|
|
20844
|
-
const apksPath =
|
|
21072
|
+
await mkdir8(options.artifactDirectory, { recursive: true });
|
|
21073
|
+
const apksPath = join10(options.artifactDirectory, "release.apks");
|
|
20845
21074
|
const windowsJava = (options.host ?? detectAbsoluteMobileHost()) === "wsl" && options.java.toLowerCase().endsWith(".exe");
|
|
20846
21075
|
const toolPath = async (path) => {
|
|
20847
21076
|
if (!windowsJava)
|
|
@@ -20896,12 +21125,12 @@ var runAbsoluteAndroidReleaseAcceptance = async (options) => {
|
|
|
20896
21125
|
]);
|
|
20897
21126
|
await requireSuccess(run, adb(options.adb, options.serial, "shell", "am", "force-stop", options.release.metadata.appId), "Android release stop");
|
|
20898
21127
|
launchMs = await launch(run, options.adb, options.serial, options.release.metadata.appId, options.launchUrl);
|
|
20899
|
-
await waitForMarker(run, options.adb, options.serial, options.release.metadata.appId, RELEASE_READY_TIMEOUT_MS);
|
|
21128
|
+
await waitForMarker(run, options.adb, options.serial, options.release.metadata.appId, options.release.metadata.engine, RELEASE_READY_TIMEOUT_MS);
|
|
20900
21129
|
await Bun.sleep(options.stabilityMs ?? RELEASE_STABILITY_MS);
|
|
20901
21130
|
await requireSuccess(run, adb(options.adb, options.serial, "shell", "am", "force-stop", options.release.metadata.appId), "Android release relaunch stop");
|
|
20902
21131
|
await requireSuccess(run, adb(options.adb, options.serial, "logcat", "-c"), "Android release relaunch log isolation");
|
|
20903
21132
|
relaunchMs = await launch(run, options.adb, options.serial, options.release.metadata.appId, options.launchUrl);
|
|
20904
|
-
await waitForMarker(run, options.adb, options.serial, options.release.metadata.appId, RELEASE_READY_TIMEOUT_MS);
|
|
21133
|
+
await waitForMarker(run, options.adb, options.serial, options.release.metadata.appId, options.release.metadata.engine, RELEASE_READY_TIMEOUT_MS);
|
|
20905
21134
|
} finally {
|
|
20906
21135
|
await Promise.all([
|
|
20907
21136
|
requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "wifi", wifi.stdout.trim() === "1" ? "enable" : "disable"), "Android Wi-Fi restore"),
|
|
@@ -20928,6 +21157,452 @@ var runAbsoluteAndroidReleaseAcceptance = async (options) => {
|
|
|
20928
21157
|
};
|
|
20929
21158
|
};
|
|
20930
21159
|
|
|
21160
|
+
// src/mobile/iosReleaseAcceptance.ts
|
|
21161
|
+
import { createHash as createHash4 } from "crypto";
|
|
21162
|
+
import {
|
|
21163
|
+
access as access7,
|
|
21164
|
+
mkdir as mkdir9,
|
|
21165
|
+
mkdtemp as mkdtemp3,
|
|
21166
|
+
readFile as readFile12,
|
|
21167
|
+
readdir as readdir5,
|
|
21168
|
+
rm as rm6,
|
|
21169
|
+
stat as stat4
|
|
21170
|
+
} from "fs/promises";
|
|
21171
|
+
import {
|
|
21172
|
+
basename as basename2,
|
|
21173
|
+
dirname as dirname9,
|
|
21174
|
+
isAbsolute as isAbsolute4,
|
|
21175
|
+
join as join11,
|
|
21176
|
+
relative as relative6,
|
|
21177
|
+
resolve as resolve7,
|
|
21178
|
+
sep as sep4
|
|
21179
|
+
} from "path";
|
|
21180
|
+
|
|
21181
|
+
// src/mobile/iosUpgradeConformance.ts
|
|
21182
|
+
var captureCommand3 = async (command) => {
|
|
21183
|
+
const process2 = Bun.spawn(command, { stderr: "pipe", stdout: "pipe" });
|
|
21184
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
21185
|
+
process2.exited,
|
|
21186
|
+
new Response(process2.stderr).text(),
|
|
21187
|
+
new Response(process2.stdout).text()
|
|
21188
|
+
]);
|
|
21189
|
+
return { exitCode, stderr, stdout };
|
|
21190
|
+
};
|
|
21191
|
+
var requiredOutput = (result, message) => {
|
|
21192
|
+
const value = result.stdout.trim();
|
|
21193
|
+
if (result.exitCode !== 0 || !value)
|
|
21194
|
+
throw new Error(`${message}: ${result.stderr.trim() || value || "no output"}`);
|
|
21195
|
+
return value;
|
|
21196
|
+
};
|
|
21197
|
+
var inspectAbsoluteIosInstalledApp = async (xcrun, udid, appId, run = captureCommand3, plutil = "/usr/bin/plutil") => {
|
|
21198
|
+
const dataContainer = requiredOutput(await run([xcrun, "simctl", "get_app_container", udid, appId, "data"]), `Could not inspect the installed iOS data container for ${appId}`);
|
|
21199
|
+
const appContainer = requiredOutput(await run([xcrun, "simctl", "get_app_container", udid, appId, "app"]), `Could not inspect the installed iOS application container for ${appId}`);
|
|
21200
|
+
const info = requiredOutput(await run([
|
|
21201
|
+
plutil,
|
|
21202
|
+
"-convert",
|
|
21203
|
+
"json",
|
|
21204
|
+
"-o",
|
|
21205
|
+
"-",
|
|
21206
|
+
`${appContainer}/Info.plist`
|
|
21207
|
+
]), `Could not inspect the installed iOS Info.plist for ${appId}`);
|
|
21208
|
+
return parseAbsoluteIosInstalledAppInfo(appId, appContainer, dataContainer, info);
|
|
21209
|
+
};
|
|
21210
|
+
var parseAbsoluteIosInstalledAppInfo = (appId, appContainer, dataContainer, output) => {
|
|
21211
|
+
let value;
|
|
21212
|
+
try {
|
|
21213
|
+
value = JSON.parse(output);
|
|
21214
|
+
} catch {
|
|
21215
|
+
throw new TypeError("Invalid installed iOS application Info.plist JSON.");
|
|
21216
|
+
}
|
|
21217
|
+
if (!value || typeof value !== "object")
|
|
21218
|
+
throw new TypeError("Invalid installed iOS application Info.plist JSON.");
|
|
21219
|
+
const bundleId = Reflect.get(value, "CFBundleIdentifier");
|
|
21220
|
+
if (bundleId !== appId)
|
|
21221
|
+
throw new Error(`Installed iOS application identifier mismatch (${String(bundleId)} != ${appId}).`);
|
|
21222
|
+
const buildNumber = Reflect.get(value, "CFBundleVersion");
|
|
21223
|
+
const version = Reflect.get(value, "CFBundleShortVersionString");
|
|
21224
|
+
return {
|
|
21225
|
+
appContainer,
|
|
21226
|
+
appId,
|
|
21227
|
+
...typeof buildNumber === "string" || typeof buildNumber === "number" ? { buildNumber: String(buildNumber) } : {},
|
|
21228
|
+
dataContainer,
|
|
21229
|
+
...typeof version === "string" ? { version } : {}
|
|
21230
|
+
};
|
|
21231
|
+
};
|
|
21232
|
+
|
|
21233
|
+
// src/mobile/iosReleaseAcceptance.ts
|
|
21234
|
+
var READY_MARKERS = {
|
|
21235
|
+
capacitor: "Capacitor embedded web content ready",
|
|
21236
|
+
expo: "Expo embedded web content ready"
|
|
21237
|
+
};
|
|
21238
|
+
var READY_TIMEOUT_MS = 30000;
|
|
21239
|
+
var READY_POLL_MS = 250;
|
|
21240
|
+
var defaultRun2 = async (command, options = {}) => {
|
|
21241
|
+
const process2 = Bun.spawn(command, {
|
|
21242
|
+
cwd: options.cwd,
|
|
21243
|
+
env: options.env,
|
|
21244
|
+
stderr: "pipe",
|
|
21245
|
+
stdin: "ignore",
|
|
21246
|
+
stdout: "pipe"
|
|
21247
|
+
});
|
|
21248
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
21249
|
+
process2.exited,
|
|
21250
|
+
new Response(process2.stderr).text(),
|
|
21251
|
+
new Response(process2.stdout).text()
|
|
21252
|
+
]);
|
|
21253
|
+
return { exitCode, stderr, stdout };
|
|
21254
|
+
};
|
|
21255
|
+
var exists3 = async (path) => {
|
|
21256
|
+
try {
|
|
21257
|
+
await access7(path);
|
|
21258
|
+
return true;
|
|
21259
|
+
} catch {
|
|
21260
|
+
return false;
|
|
21261
|
+
}
|
|
21262
|
+
};
|
|
21263
|
+
var sha2562 = async (path) => createHash4("sha256").update(await readFile12(path)).digest("hex");
|
|
21264
|
+
var inside2 = (root, candidate) => {
|
|
21265
|
+
const path = relative6(resolve7(root), resolve7(candidate));
|
|
21266
|
+
return path === "" || !isAbsolute4(path) && path !== ".." && !path.startsWith(`..${sep4}`);
|
|
21267
|
+
};
|
|
21268
|
+
var requireSuccess2 = async (run, command, label, options) => {
|
|
21269
|
+
const result = await run(command, options);
|
|
21270
|
+
if (result.exitCode !== 0)
|
|
21271
|
+
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
|
|
21272
|
+
return result;
|
|
21273
|
+
};
|
|
21274
|
+
var findByExtension = async (root, extension) => {
|
|
21275
|
+
if (!await exists3(root))
|
|
21276
|
+
return;
|
|
21277
|
+
const entries = await readdir5(root, { withFileTypes: true });
|
|
21278
|
+
const matches = await Promise.all(entries.map(async (entry) => {
|
|
21279
|
+
const path = join11(root, entry.name);
|
|
21280
|
+
if (entry.name.endsWith(extension))
|
|
21281
|
+
return path;
|
|
21282
|
+
return entry.isDirectory() ? findByExtension(path, extension) : undefined;
|
|
21283
|
+
}));
|
|
21284
|
+
return matches.find((match) => match !== undefined);
|
|
21285
|
+
};
|
|
21286
|
+
var readAbsoluteIosRelease = async (projectRoot, requested) => {
|
|
21287
|
+
const root = resolve7(projectRoot);
|
|
21288
|
+
const selected = resolve7(root, requested);
|
|
21289
|
+
if (!inside2(root, selected))
|
|
21290
|
+
throw new TypeError("mobile test ios --release must remain inside the project.");
|
|
21291
|
+
const metadataPath = basename2(selected) === "release.json" ? selected : join11(selected, "release.json");
|
|
21292
|
+
const releaseRoot = dirname9(metadataPath);
|
|
21293
|
+
const metadata = requireAbsoluteIosReleaseMetadata(JSON.parse(await readFile12(metadataPath, "utf8")));
|
|
21294
|
+
const artifactPath = resolve7(releaseRoot, metadata.artifact);
|
|
21295
|
+
if (!inside2(releaseRoot, artifactPath))
|
|
21296
|
+
throw new TypeError("iOS release artifact path is invalid.");
|
|
21297
|
+
const [artifactStat, artifactSha256] = await Promise.all([
|
|
21298
|
+
stat4(artifactPath),
|
|
21299
|
+
sha2562(artifactPath)
|
|
21300
|
+
]);
|
|
21301
|
+
if (artifactStat.size !== metadata.bytes || artifactSha256 !== metadata.sha256)
|
|
21302
|
+
throw new TypeError(`Immutable iOS release ${metadata.releaseId} is missing or modified.`);
|
|
21303
|
+
const registered = metadata.registeredDevice;
|
|
21304
|
+
let registeredArtifactPath;
|
|
21305
|
+
if (registered) {
|
|
21306
|
+
registeredArtifactPath = resolve7(releaseRoot, registered.artifact);
|
|
21307
|
+
if (!inside2(releaseRoot, registeredArtifactPath))
|
|
21308
|
+
throw new TypeError("iOS registered-device artifact path is invalid.");
|
|
21309
|
+
const [registeredStat, registeredSha256] = await Promise.all([
|
|
21310
|
+
stat4(registeredArtifactPath),
|
|
21311
|
+
sha2562(registeredArtifactPath)
|
|
21312
|
+
]);
|
|
21313
|
+
if (registeredStat.size !== registered.bytes || registeredSha256 !== registered.sha256)
|
|
21314
|
+
throw new TypeError(`Registered-device export for ${metadata.releaseId} is missing or modified.`);
|
|
21315
|
+
}
|
|
21316
|
+
return {
|
|
21317
|
+
artifactPath,
|
|
21318
|
+
metadata,
|
|
21319
|
+
metadataPath,
|
|
21320
|
+
registeredArtifactPath,
|
|
21321
|
+
releaseRoot
|
|
21322
|
+
};
|
|
21323
|
+
};
|
|
21324
|
+
var releaseMarker = (release) => {
|
|
21325
|
+
const marker = READY_MARKERS[release.metadata.engine];
|
|
21326
|
+
return release.metadata.buildNumber === undefined ? marker : `${marker}; version=${release.metadata.marketingVersion}; build=${release.metadata.buildNumber}`;
|
|
21327
|
+
};
|
|
21328
|
+
var waitForSimulatorMarker = async (options) => {
|
|
21329
|
+
const startedAt = performance.now();
|
|
21330
|
+
const poll = async () => {
|
|
21331
|
+
if (performance.now() - startedAt >= READY_TIMEOUT_MS)
|
|
21332
|
+
throw new Error("The installed iOS Release app launched but its embedded content did not become ready.");
|
|
21333
|
+
const logs = await options.run([
|
|
21334
|
+
options.xcrun,
|
|
21335
|
+
"simctl",
|
|
21336
|
+
"spawn",
|
|
21337
|
+
options.udid,
|
|
21338
|
+
"log",
|
|
21339
|
+
"show",
|
|
21340
|
+
"--style",
|
|
21341
|
+
"compact",
|
|
21342
|
+
"--start",
|
|
21343
|
+
options.startedAt.toISOString(),
|
|
21344
|
+
"--predicate",
|
|
21345
|
+
`eventMessage CONTAINS "${releaseMarker(options.release)}"`
|
|
21346
|
+
]);
|
|
21347
|
+
if (logs.exitCode === 0 && logs.stdout.includes(releaseMarker(options.release)))
|
|
21348
|
+
return;
|
|
21349
|
+
await Bun.sleep(READY_POLL_MS);
|
|
21350
|
+
await poll();
|
|
21351
|
+
};
|
|
21352
|
+
await poll();
|
|
21353
|
+
};
|
|
21354
|
+
var launchSimulator = async (options) => {
|
|
21355
|
+
const startedAt = performance.now();
|
|
21356
|
+
const logStartedAt = new Date;
|
|
21357
|
+
await requireSuccess2(options.run, [
|
|
21358
|
+
options.xcrun,
|
|
21359
|
+
"simctl",
|
|
21360
|
+
"launch",
|
|
21361
|
+
"--terminate-running-process",
|
|
21362
|
+
options.udid,
|
|
21363
|
+
options.release.metadata.appId
|
|
21364
|
+
], "iOS Simulator Release launch");
|
|
21365
|
+
await waitForSimulatorMarker({ ...options, startedAt: logStartedAt });
|
|
21366
|
+
return Math.round(performance.now() - startedAt);
|
|
21367
|
+
};
|
|
21368
|
+
var runAbsoluteIosSimulatorReleaseAcceptance = async (options) => {
|
|
21369
|
+
const run = options.run ?? defaultRun2;
|
|
21370
|
+
const xcodebuild = options.xcodebuild ?? "/usr/bin/xcodebuild";
|
|
21371
|
+
const xcrun = options.xcrun ?? "/usr/bin/xcrun";
|
|
21372
|
+
const startedAt = performance.now();
|
|
21373
|
+
const iosRoot = join11(options.config.nativeProjectDirectory, "ios");
|
|
21374
|
+
const xcode = await resolveAbsoluteIosXcodeProject(iosRoot);
|
|
21375
|
+
const derivedData = join11(options.artifactDirectory, "DerivedData");
|
|
21376
|
+
await mkdir9(options.artifactDirectory, { recursive: true });
|
|
21377
|
+
await requireSuccess2(run, [
|
|
21378
|
+
xcodebuild,
|
|
21379
|
+
"-workspace",
|
|
21380
|
+
xcode.workspacePath,
|
|
21381
|
+
"-scheme",
|
|
21382
|
+
xcode.scheme,
|
|
21383
|
+
"-configuration",
|
|
21384
|
+
"Release",
|
|
21385
|
+
"-destination",
|
|
21386
|
+
`platform=iOS Simulator,id=${options.udid}`,
|
|
21387
|
+
"-derivedDataPath",
|
|
21388
|
+
derivedData,
|
|
21389
|
+
`MARKETING_VERSION=${options.release.metadata.marketingVersion}`,
|
|
21390
|
+
...options.release.metadata.buildNumber === undefined ? [] : [
|
|
21391
|
+
`CURRENT_PROJECT_VERSION=${options.release.metadata.buildNumber}`
|
|
21392
|
+
],
|
|
21393
|
+
"build"
|
|
21394
|
+
], "iOS Simulator Release build", { cwd: iosRoot });
|
|
21395
|
+
const app = await findByExtension(join11(derivedData, "Build", "Products"), ".app");
|
|
21396
|
+
if (!app)
|
|
21397
|
+
throw new Error("Xcode did not produce a Simulator Release app.");
|
|
21398
|
+
const installStartedAt = performance.now();
|
|
21399
|
+
await requireSuccess2(run, [xcrun, "simctl", "install", options.udid, app], "iOS Simulator Release installation");
|
|
21400
|
+
const installMs = Math.round(performance.now() - installStartedAt);
|
|
21401
|
+
const installed = await inspectAbsoluteIosInstalledApp(xcrun, options.udid, options.release.metadata.appId, (command) => run(command));
|
|
21402
|
+
if (installed.version !== options.release.metadata.marketingVersion)
|
|
21403
|
+
throw new Error(`Installed iOS version ${installed.version ?? "unknown"} does not match release ${options.release.metadata.marketingVersion}.`);
|
|
21404
|
+
if (options.release.metadata.buildNumber !== undefined && installed.buildNumber !== String(options.release.metadata.buildNumber))
|
|
21405
|
+
throw new Error(`Installed iOS build ${installed.buildNumber ?? "unknown"} does not match release ${options.release.metadata.buildNumber}.`);
|
|
21406
|
+
const launchMs = await launchSimulator({
|
|
21407
|
+
release: options.release,
|
|
21408
|
+
run,
|
|
21409
|
+
udid: options.udid,
|
|
21410
|
+
xcrun
|
|
21411
|
+
});
|
|
21412
|
+
await requireSuccess2(run, [
|
|
21413
|
+
xcrun,
|
|
21414
|
+
"simctl",
|
|
21415
|
+
"terminate",
|
|
21416
|
+
options.udid,
|
|
21417
|
+
options.release.metadata.appId
|
|
21418
|
+
], "iOS Simulator Release termination");
|
|
21419
|
+
const relaunchMs = await launchSimulator({
|
|
21420
|
+
release: options.release,
|
|
21421
|
+
run,
|
|
21422
|
+
udid: options.udid,
|
|
21423
|
+
xcrun
|
|
21424
|
+
});
|
|
21425
|
+
return {
|
|
21426
|
+
artifactBytes: options.release.metadata.bytes,
|
|
21427
|
+
artifactExactness: "source-equivalent",
|
|
21428
|
+
artifactSha256: options.release.metadata.sha256,
|
|
21429
|
+
...options.release.metadata.buildNumber === undefined ? {} : { buildNumber: options.release.metadata.buildNumber },
|
|
21430
|
+
distribution: "simulator-release",
|
|
21431
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
21432
|
+
embeddedLocal: true,
|
|
21433
|
+
engine: options.release.metadata.engine,
|
|
21434
|
+
installMs,
|
|
21435
|
+
launchMs,
|
|
21436
|
+
marketingVersion: options.release.metadata.marketingVersion,
|
|
21437
|
+
networkUnavailable: "not-proven",
|
|
21438
|
+
relaunchMs,
|
|
21439
|
+
releaseId: options.release.metadata.releaseId,
|
|
21440
|
+
signed: options.release.metadata.signed,
|
|
21441
|
+
status: "pass",
|
|
21442
|
+
target: "simulator"
|
|
21443
|
+
};
|
|
21444
|
+
};
|
|
21445
|
+
var readUntilMarker = async (reader, marker, output = "") => {
|
|
21446
|
+
const { done, value } = await reader.read();
|
|
21447
|
+
if (done)
|
|
21448
|
+
return { found: false, output };
|
|
21449
|
+
const next = `${output}${new TextDecoder().decode(value)}`;
|
|
21450
|
+
if (next.includes(marker))
|
|
21451
|
+
return { found: true, output: next };
|
|
21452
|
+
return readUntilMarker(reader, marker, next);
|
|
21453
|
+
};
|
|
21454
|
+
var observeDeviceLaunch = async (options) => {
|
|
21455
|
+
const startedAt = performance.now();
|
|
21456
|
+
const process2 = Bun.spawn([
|
|
21457
|
+
options.xcrun,
|
|
21458
|
+
"devicectl",
|
|
21459
|
+
"device",
|
|
21460
|
+
"process",
|
|
21461
|
+
"launch",
|
|
21462
|
+
"--console",
|
|
21463
|
+
"--terminate-existing",
|
|
21464
|
+
"--device",
|
|
21465
|
+
options.device,
|
|
21466
|
+
options.appId
|
|
21467
|
+
], { stderr: "pipe", stdin: "ignore", stdout: "pipe" });
|
|
21468
|
+
const stdoutReader = process2.stdout.getReader();
|
|
21469
|
+
const stderrReader = process2.stderr.getReader();
|
|
21470
|
+
const requireMarker = async (reader) => {
|
|
21471
|
+
const result = await readUntilMarker(reader, options.marker);
|
|
21472
|
+
if (!result.found)
|
|
21473
|
+
throw new Error(`Console stream closed before readiness.${result.output.trim() ? ` Output: ${result.output.trim()}` : ""}`);
|
|
21474
|
+
return result;
|
|
21475
|
+
};
|
|
21476
|
+
const observed = Promise.any([
|
|
21477
|
+
requireMarker(stdoutReader),
|
|
21478
|
+
requireMarker(stderrReader)
|
|
21479
|
+
]);
|
|
21480
|
+
try {
|
|
21481
|
+
const result = await Promise.race([
|
|
21482
|
+
observed.then((value) => ({ type: "output", value })).catch(() => ({ type: "closed" })),
|
|
21483
|
+
Bun.sleep(options.timeoutMs ?? READY_TIMEOUT_MS).then(() => ({
|
|
21484
|
+
type: "timeout"
|
|
21485
|
+
}))
|
|
21486
|
+
]);
|
|
21487
|
+
if (result.type === "output" && result.value.found)
|
|
21488
|
+
return Math.round(performance.now() - startedAt);
|
|
21489
|
+
if (result.type === "closed")
|
|
21490
|
+
throw new Error("The physical iOS app exited before embedded content became ready.");
|
|
21491
|
+
throw new Error("The physical iOS app did not report embedded readiness before the timeout.");
|
|
21492
|
+
} finally {
|
|
21493
|
+
process2.kill();
|
|
21494
|
+
await Promise.all([
|
|
21495
|
+
stdoutReader.cancel().catch(() => {
|
|
21496
|
+
return;
|
|
21497
|
+
}),
|
|
21498
|
+
stderrReader.cancel().catch(() => {
|
|
21499
|
+
return;
|
|
21500
|
+
})
|
|
21501
|
+
]);
|
|
21502
|
+
stdoutReader.releaseLock();
|
|
21503
|
+
stderrReader.releaseLock();
|
|
21504
|
+
}
|
|
21505
|
+
};
|
|
21506
|
+
var installRegisteredDeviceRelease = async (options) => {
|
|
21507
|
+
if (!options.release.registeredArtifactPath)
|
|
21508
|
+
throw new TypeError("This release has no same-archive registered-device IPA. Rebuild with `absolute mobile build ios --registered-device-artifact`.");
|
|
21509
|
+
const staging = await mkdtemp3(join11(dirname9(options.release.releaseRoot), ".ios-device-"));
|
|
21510
|
+
try {
|
|
21511
|
+
await requireSuccess2(options.run, [
|
|
21512
|
+
"/usr/bin/ditto",
|
|
21513
|
+
"-x",
|
|
21514
|
+
"-k",
|
|
21515
|
+
options.release.registeredArtifactPath,
|
|
21516
|
+
staging
|
|
21517
|
+
], "iOS registered-device IPA extraction");
|
|
21518
|
+
const app = await findByExtension(staging, ".app");
|
|
21519
|
+
if (!app)
|
|
21520
|
+
throw new Error("The registered-device IPA contains no installable app.");
|
|
21521
|
+
const startedAt = performance.now();
|
|
21522
|
+
await requireSuccess2(options.run, [
|
|
21523
|
+
options.xcrun,
|
|
21524
|
+
"devicectl",
|
|
21525
|
+
"device",
|
|
21526
|
+
"install",
|
|
21527
|
+
"app",
|
|
21528
|
+
"--device",
|
|
21529
|
+
options.device,
|
|
21530
|
+
app
|
|
21531
|
+
], "iOS registered-device Release installation");
|
|
21532
|
+
return Math.round(performance.now() - startedAt);
|
|
21533
|
+
} finally {
|
|
21534
|
+
await rm6(staging, { force: true, recursive: true });
|
|
21535
|
+
}
|
|
21536
|
+
};
|
|
21537
|
+
var inspectTestFlightInstallation = async (options) => {
|
|
21538
|
+
const apps = await requireSuccess2(options.run, [
|
|
21539
|
+
options.xcrun,
|
|
21540
|
+
"devicectl",
|
|
21541
|
+
"device",
|
|
21542
|
+
"info",
|
|
21543
|
+
"apps",
|
|
21544
|
+
"--device",
|
|
21545
|
+
options.device
|
|
21546
|
+
], "TestFlight installation inspection");
|
|
21547
|
+
if (!apps.stdout.includes(options.release.metadata.appId))
|
|
21548
|
+
throw new Error("The selected device does not have the requested TestFlight app installed.");
|
|
21549
|
+
return 0;
|
|
21550
|
+
};
|
|
21551
|
+
var runAbsoluteIosDeviceReleaseAcceptance = async (options) => {
|
|
21552
|
+
if (!options.networkUnavailableConfirmed)
|
|
21553
|
+
throw new TypeError("Physical iOS offline acceptance requires explicit confirmation that Airplane Mode is enabled and Wi-Fi is disabled in Settings.");
|
|
21554
|
+
const run = options.run ?? defaultRun2;
|
|
21555
|
+
const xcrun = options.xcrun ?? "/usr/bin/xcrun";
|
|
21556
|
+
const startedAt = performance.now();
|
|
21557
|
+
await requireSuccess2(run, [
|
|
21558
|
+
xcrun,
|
|
21559
|
+
"devicectl",
|
|
21560
|
+
"device",
|
|
21561
|
+
"info",
|
|
21562
|
+
"details",
|
|
21563
|
+
"--device",
|
|
21564
|
+
options.device
|
|
21565
|
+
], "iOS physical-device availability inspection");
|
|
21566
|
+
const installation = {
|
|
21567
|
+
device: options.device,
|
|
21568
|
+
release: options.release,
|
|
21569
|
+
run,
|
|
21570
|
+
xcrun
|
|
21571
|
+
};
|
|
21572
|
+
const installMs = options.distribution === "registered-device" ? await installRegisteredDeviceRelease(installation) : await inspectTestFlightInstallation(installation);
|
|
21573
|
+
const observe = options.observeLaunch ?? ((launch2) => observeDeviceLaunch({ ...launch2, xcrun }));
|
|
21574
|
+
const marker = releaseMarker(options.release);
|
|
21575
|
+
const launchMs = await observe({
|
|
21576
|
+
appId: options.release.metadata.appId,
|
|
21577
|
+
device: options.device,
|
|
21578
|
+
marker
|
|
21579
|
+
});
|
|
21580
|
+
const relaunchMs = await observe({
|
|
21581
|
+
appId: options.release.metadata.appId,
|
|
21582
|
+
device: options.device,
|
|
21583
|
+
marker
|
|
21584
|
+
});
|
|
21585
|
+
return {
|
|
21586
|
+
artifactBytes: options.release.metadata.bytes,
|
|
21587
|
+
artifactExactness: options.distribution === "testflight" ? "store-delivered" : "archive-equivalent",
|
|
21588
|
+
artifactSha256: options.release.metadata.sha256,
|
|
21589
|
+
...options.release.metadata.buildNumber === undefined ? {} : { buildNumber: options.release.metadata.buildNumber },
|
|
21590
|
+
distribution: options.distribution === "testflight" ? "apple-processed" : "registered-device",
|
|
21591
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
21592
|
+
embeddedLocal: true,
|
|
21593
|
+
engine: options.release.metadata.engine,
|
|
21594
|
+
installMs,
|
|
21595
|
+
launchMs,
|
|
21596
|
+
marketingVersion: options.release.metadata.marketingVersion,
|
|
21597
|
+
networkUnavailable: "user-confirmed",
|
|
21598
|
+
relaunchMs,
|
|
21599
|
+
releaseId: options.release.metadata.releaseId,
|
|
21600
|
+
signed: options.release.metadata.signed,
|
|
21601
|
+
status: "pass",
|
|
21602
|
+
target: "device"
|
|
21603
|
+
};
|
|
21604
|
+
};
|
|
21605
|
+
|
|
20931
21606
|
// src/mobile/iosDeviceAcceptance.ts
|
|
20932
21607
|
var absoluteIosDeviceAcceptanceCommands = (options) => {
|
|
20933
21608
|
const xcrun = options.xcrun ?? "/usr/bin/xcrun";
|
|
@@ -20946,20 +21621,20 @@ var absoluteIosDeviceAcceptanceCommands = (options) => {
|
|
|
20946
21621
|
]
|
|
20947
21622
|
};
|
|
20948
21623
|
};
|
|
20949
|
-
var
|
|
21624
|
+
var requireSuccess3 = (result, message) => {
|
|
20950
21625
|
if (result.exitCode !== 0)
|
|
20951
21626
|
throw new Error(message);
|
|
20952
21627
|
return result;
|
|
20953
21628
|
};
|
|
20954
21629
|
var testAbsoluteIosPhysicalDevice = async (options) => {
|
|
20955
21630
|
const commands = absoluteIosDeviceAcceptanceCommands(options);
|
|
20956
|
-
|
|
20957
|
-
const apps =
|
|
21631
|
+
requireSuccess3(await options.capture(commands.details), "The selected physical iOS device is unavailable. Pair it in Xcode Device Hub, trust this Mac, unlock it, and enable Developer Mode.");
|
|
21632
|
+
const apps = requireSuccess3(await options.capture(commands.apps), "AbsoluteJS could not inspect installed apps on the physical iOS device.");
|
|
20958
21633
|
if (!apps.stdout.includes(options.appId))
|
|
20959
21634
|
throw new Error("The AbsoluteJS app is not installed on the selected physical iOS device. Start bun dev with the same --ios-device value first.");
|
|
20960
21635
|
const now = options.now ?? performance.now.bind(performance);
|
|
20961
21636
|
const startedAt = now();
|
|
20962
|
-
|
|
21637
|
+
requireSuccess3(await options.capture(commands.launch), "AbsoluteJS could not relaunch the app on the physical iOS device.");
|
|
20963
21638
|
await options.waitForHmr();
|
|
20964
21639
|
return {
|
|
20965
21640
|
hmrConnected: true,
|
|
@@ -20969,7 +21644,7 @@ var testAbsoluteIosPhysicalDevice = async (options) => {
|
|
|
20969
21644
|
};
|
|
20970
21645
|
|
|
20971
21646
|
// src/mobile/iosConformance.ts
|
|
20972
|
-
import { readFile as
|
|
21647
|
+
import { readFile as readFile13, stat as stat5 } from "fs/promises";
|
|
20973
21648
|
var HMR_LINE = new RegExp(String.raw`\[hmr:ios\]\s+([^\n]*?)\s+(applied in|falling back to reload after|failed after)\s+(\d+)ms(?:; server\s+(\d+)ms, client\s+(\d+)ms)?`, "u");
|
|
20974
21649
|
var parseAbsoluteIosHmrLog = (line) => {
|
|
20975
21650
|
const match = HMR_LINE.exec(line);
|
|
@@ -21001,13 +21676,13 @@ var waitForAbsoluteIosHmrLog = async (options) => {
|
|
|
21001
21676
|
const sleep = options.sleep ?? Bun.sleep;
|
|
21002
21677
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
21003
21678
|
const deadline = Date.now() + timeoutMs;
|
|
21004
|
-
let offset = options.startOffset ?? await
|
|
21679
|
+
let offset = options.startOffset ?? await stat5(options.logPath).then(({ size }) => size).catch(() => 0);
|
|
21005
21680
|
let buffered = "";
|
|
21006
21681
|
const poll = async () => {
|
|
21007
21682
|
if (Date.now() > deadline)
|
|
21008
21683
|
throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
|
|
21009
21684
|
options.signal?.throwIfAborted();
|
|
21010
|
-
const contents = await
|
|
21685
|
+
const contents = await readFile13(options.logPath).catch(() => Buffer.alloc(0));
|
|
21011
21686
|
if (contents.byteLength < offset) {
|
|
21012
21687
|
offset = 0;
|
|
21013
21688
|
buffered = "";
|
|
@@ -21028,8 +21703,8 @@ var waitForAbsoluteIosHmrLog = async (options) => {
|
|
|
21028
21703
|
};
|
|
21029
21704
|
|
|
21030
21705
|
// src/mobile/nativeTestReport.ts
|
|
21031
|
-
import { mkdir as
|
|
21032
|
-
import { join as
|
|
21706
|
+
import { mkdir as mkdir10, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
|
|
21707
|
+
import { join as join12 } from "path";
|
|
21033
21708
|
var secretPattern = /(authorization|access[_ -]?token|refresh[_ -]?token|socket[_ -]?ticket|password|cookie)(\s*[=:]\s*)([^\s,;]+)/giu;
|
|
21034
21709
|
var bearerPattern = /bearer\s+[^\s,;]+/giu;
|
|
21035
21710
|
var coordinatePattern = /\b(latitude|longitude|lat|lng)(\s*[=:]\s*)-?\d+(?:\.\d+)?/giu;
|
|
@@ -21056,8 +21731,10 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
|
|
|
21056
21731
|
let developmentDetails = run.status === "pass" ? `The app launched and connected to native HMR in ${run.durationMs}ms.${routeDetails}` : `The automated native run failed after ${run.durationMs}ms: ${run.error ?? "No error detail was available."}`;
|
|
21057
21732
|
if (run.release)
|
|
21058
21733
|
developmentDetails = run.status === "pass" ? `The ${run.release.engine} release was installed from its generated APK set, launched offline in ${run.release.launchMs}ms, and relaunched in ${run.release.relaunchMs}ms.` : `Installed ${run.release.engine} release acceptance failed after ${run.durationMs}ms.`;
|
|
21734
|
+
if (run.iosRelease)
|
|
21735
|
+
developmentDetails = run.status === "pass" ? `The ${run.iosRelease.engine} iOS release rendered embedded local content in ${run.iosRelease.launchMs}ms and relaunched in ${run.iosRelease.relaunchMs}ms.` : `Installed ${run.iosRelease.engine} iOS release acceptance failed after ${run.durationMs}ms.`;
|
|
21059
21736
|
let hmrDetails = "Correlated edit timing was not requested. Rerun with --wait-for-hmr.";
|
|
21060
|
-
if (run.release)
|
|
21737
|
+
if (run.release || run.iosRelease)
|
|
21061
21738
|
hmrDetails = "HMR is intentionally not part of installed production-release acceptance.";
|
|
21062
21739
|
if (run.hmr)
|
|
21063
21740
|
hmrDetails = `Observed native HMR ${run.hmr.outcome} in ${run.hmr.durationMs}ms${run.hmr.serverMs === undefined ? "" : ` (server ${run.hmr.serverMs}ms, client ${run.hmr.clientMs}ms)`}.`;
|
|
@@ -21096,6 +21773,18 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
|
|
|
21096
21773
|
result: run.release.embeddedOffline ? "PASS" : "FAIL"
|
|
21097
21774
|
});
|
|
21098
21775
|
}
|
|
21776
|
+
if (run.iosRelease) {
|
|
21777
|
+
const release = run.iosRelease;
|
|
21778
|
+
checks.push({
|
|
21779
|
+
details: `Validated immutable release ${release.releaseId} (${release.artifactBytes} bytes, SHA-256 ${release.artifactSha256}); ${release.distribution} evidence is ${release.artifactExactness}, installed in ${release.installMs}ms.`,
|
|
21780
|
+
id: "AUTO-IOS-RELEASE-01",
|
|
21781
|
+
result: run.status === "pass" ? "PASS" : "FAIL"
|
|
21782
|
+
}, {
|
|
21783
|
+
details: release.networkUnavailable === "user-confirmed" ? "The tester explicitly confirmed Airplane Mode was enabled and Wi-Fi was disabled before both physical-device launches." : "Simulator Release proved embedded local rendering, but does not claim physical-device network-unavailable behavior.",
|
|
21784
|
+
id: "AUTO-IOS-RELEASE-OFFLINE-01",
|
|
21785
|
+
result: release.networkUnavailable === "user-confirmed" ? "PASS" : "SKIPPED"
|
|
21786
|
+
});
|
|
21787
|
+
}
|
|
21099
21788
|
if (run.upgrade) {
|
|
21100
21789
|
const { upgrade } = run;
|
|
21101
21790
|
checks.push({
|
|
@@ -21158,7 +21847,7 @@ var createAbsoluteNativeTestReport = (options) => ({
|
|
|
21158
21847
|
run: options.run
|
|
21159
21848
|
});
|
|
21160
21849
|
var readPackageVersionForNativeReport = async (packageJsonPath) => {
|
|
21161
|
-
const manifest = JSON.parse(await
|
|
21850
|
+
const manifest = JSON.parse(await readFile14(packageJsonPath, "utf8"));
|
|
21162
21851
|
if (typeof manifest !== "object" || manifest === null)
|
|
21163
21852
|
return "unknown";
|
|
21164
21853
|
const version = Reflect.get(manifest, "version");
|
|
@@ -21196,13 +21885,13 @@ ${table(report.manualChecks)}
|
|
|
21196
21885
|
`;
|
|
21197
21886
|
};
|
|
21198
21887
|
var writeAbsoluteNativeTestReport = async (directory, report) => {
|
|
21199
|
-
await
|
|
21200
|
-
const jsonPath =
|
|
21201
|
-
const markdownPath =
|
|
21888
|
+
await mkdir10(directory, { recursive: true });
|
|
21889
|
+
const jsonPath = join12(directory, "report.json");
|
|
21890
|
+
const markdownPath = join12(directory, "report.md");
|
|
21202
21891
|
await Promise.all([
|
|
21203
|
-
|
|
21892
|
+
writeFile11(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
21204
21893
|
`),
|
|
21205
|
-
|
|
21894
|
+
writeFile11(markdownPath, renderAbsoluteNativeTestReport(report))
|
|
21206
21895
|
]);
|
|
21207
21896
|
return { directory, jsonPath, markdownPath };
|
|
21208
21897
|
};
|
|
@@ -21313,7 +22002,7 @@ var createAbsoluteIosPartnerReport = (options) => {
|
|
|
21313
22002
|
absolutejsVersion: options.absolutejsVersion,
|
|
21314
22003
|
bunVersion: options.bunVersion,
|
|
21315
22004
|
macosVersion: options.macosVersion,
|
|
21316
|
-
provider: "capacitor",
|
|
22005
|
+
provider: options.provider ?? "capacitor",
|
|
21317
22006
|
xcodeVersion: options.xcodeVersion
|
|
21318
22007
|
},
|
|
21319
22008
|
run: {
|
|
@@ -21454,8 +22143,8 @@ var createAbsoluteAndroidTestReport = (options) => {
|
|
|
21454
22143
|
};
|
|
21455
22144
|
|
|
21456
22145
|
// src/mobile/releasePublisher.ts
|
|
21457
|
-
import { access as
|
|
21458
|
-
import { isAbsolute as
|
|
22146
|
+
import { access as access8 } from "fs/promises";
|
|
22147
|
+
import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve8, sep as sep5 } from "path";
|
|
21459
22148
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
21460
22149
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
21461
22150
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -21481,17 +22170,17 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
|
|
|
21481
22170
|
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21482
22171
|
var isPublisher = (value) => isRecord6(value) && typeof value.publish === "function";
|
|
21483
22172
|
var publisherModulePath = (projectRoot, requested) => {
|
|
21484
|
-
const root =
|
|
21485
|
-
const path =
|
|
21486
|
-
const projectRelative =
|
|
21487
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
22173
|
+
const root = resolve8(projectRoot);
|
|
22174
|
+
const path = resolve8(root, requested);
|
|
22175
|
+
const projectRelative = relative7(root, path);
|
|
22176
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep5}`) || isAbsolute5(projectRelative)) {
|
|
21488
22177
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
21489
22178
|
}
|
|
21490
22179
|
return path;
|
|
21491
22180
|
};
|
|
21492
22181
|
var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
|
|
21493
22182
|
const modulePath = publisherModulePath(projectRoot, requestedModulePath);
|
|
21494
|
-
await
|
|
22183
|
+
await access8(modulePath).catch(() => {
|
|
21495
22184
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
21496
22185
|
});
|
|
21497
22186
|
const loaded = await import(pathToFileURL2(modulePath).href);
|
|
@@ -21553,8 +22242,8 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
21553
22242
|
};
|
|
21554
22243
|
|
|
21555
22244
|
// src/mobile/mobileInspect.ts
|
|
21556
|
-
import { access as
|
|
21557
|
-
import { join as
|
|
22245
|
+
import { access as access9, readFile as readFile15 } from "fs/promises";
|
|
22246
|
+
import { join as join13, relative as relative8, resolve as resolve9 } from "path";
|
|
21558
22247
|
var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1;
|
|
21559
22248
|
var MOBILE_PACKAGE_NAMES = new Set([
|
|
21560
22249
|
"@absolutejs/absolute",
|
|
@@ -21569,19 +22258,19 @@ var MOBILE_PACKAGE_NAMES = new Set([
|
|
|
21569
22258
|
]);
|
|
21570
22259
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21571
22260
|
var portablePath2 = (projectRoot, path) => {
|
|
21572
|
-
const value =
|
|
22261
|
+
const value = relative8(resolve9(projectRoot), resolve9(path)).replaceAll("\\", "/");
|
|
21573
22262
|
return value || ".";
|
|
21574
22263
|
};
|
|
21575
22264
|
var pathExists4 = async (path) => {
|
|
21576
22265
|
try {
|
|
21577
|
-
await
|
|
22266
|
+
await access9(path);
|
|
21578
22267
|
return true;
|
|
21579
22268
|
} catch {
|
|
21580
22269
|
return false;
|
|
21581
22270
|
}
|
|
21582
22271
|
};
|
|
21583
22272
|
var readObject2 = async (path) => {
|
|
21584
|
-
const value = JSON.parse(await
|
|
22273
|
+
const value = JSON.parse(await readFile15(path, "utf8"));
|
|
21585
22274
|
if (!isObject2(value))
|
|
21586
22275
|
throw new TypeError("JSON root must be an object.");
|
|
21587
22276
|
return value;
|
|
@@ -21593,13 +22282,13 @@ var addPackageDeclarations = (declarations, value) => {
|
|
|
21593
22282
|
declarations.set(name, declared);
|
|
21594
22283
|
};
|
|
21595
22284
|
var packageInspections = async (projectRoot, additionalNames) => {
|
|
21596
|
-
const project = await readObject2(
|
|
22285
|
+
const project = await readObject2(join13(projectRoot, "package.json"));
|
|
21597
22286
|
const declarations = new Map;
|
|
21598
22287
|
for (const field2 of ["dependencies", "devDependencies"])
|
|
21599
22288
|
addPackageDeclarations(declarations, project[field2]);
|
|
21600
22289
|
const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
|
|
21601
22290
|
return Promise.all(names.map(async (name) => {
|
|
21602
|
-
const installedManifest = await readObject2(
|
|
22291
|
+
const installedManifest = await readObject2(join13(projectRoot, "node_modules", name, "package.json")).catch(() => {
|
|
21603
22292
|
return;
|
|
21604
22293
|
});
|
|
21605
22294
|
const installed = installedManifest?.version;
|
|
@@ -21658,7 +22347,7 @@ var inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) =>
|
|
|
21658
22347
|
},
|
|
21659
22348
|
format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
|
|
21660
22349
|
nativeProjects: await Promise.all(config.platforms.map(async (platform) => {
|
|
21661
|
-
const path =
|
|
22350
|
+
const path = join13(config.nativeProjectDirectory, platform);
|
|
21662
22351
|
return {
|
|
21663
22352
|
initialized: await pathExists4(path),
|
|
21664
22353
|
path: portablePath2(projectRoot, path),
|
|
@@ -21715,8 +22404,8 @@ var renderAbsoluteMobileProjectInspection = (report) => {
|
|
|
21715
22404
|
|
|
21716
22405
|
// src/mobile/ciWorkflow.ts
|
|
21717
22406
|
import { existsSync } from "fs";
|
|
21718
|
-
import { access as
|
|
21719
|
-
import { dirname as
|
|
22407
|
+
import { access as access10, mkdir as mkdir11, readFile as readFile16, writeFile as writeFile12 } from "fs/promises";
|
|
22408
|
+
import { dirname as dirname10, extname as extname2, relative as relative9, resolve as resolve10, sep as sep6 } from "path";
|
|
21720
22409
|
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
|
|
21721
22410
|
var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
|
|
21722
22411
|
var CI_ENV_INDENTATION = 6;
|
|
@@ -21735,9 +22424,9 @@ var RESERVED_SECRET_NAMES = new Set([
|
|
|
21735
22424
|
"APP_STORE_CONNECT_KEY_ID",
|
|
21736
22425
|
"APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
|
|
21737
22426
|
]);
|
|
21738
|
-
var
|
|
22427
|
+
var exists4 = async (path) => {
|
|
21739
22428
|
try {
|
|
21740
|
-
await
|
|
22429
|
+
await access10(path);
|
|
21741
22430
|
return true;
|
|
21742
22431
|
} catch {
|
|
21743
22432
|
return false;
|
|
@@ -21745,10 +22434,10 @@ var exists3 = async (path) => {
|
|
|
21745
22434
|
};
|
|
21746
22435
|
var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
21747
22436
|
var projectPath2 = (projectRoot, value, field2, options = {}) => {
|
|
21748
|
-
const root =
|
|
21749
|
-
const path =
|
|
21750
|
-
const portable =
|
|
21751
|
-
if (portable === ".." || portable.startsWith(`..${
|
|
22437
|
+
const root = resolve10(projectRoot);
|
|
22438
|
+
const path = resolve10(root, value);
|
|
22439
|
+
const portable = relative9(root, path).replaceAll("\\", "/");
|
|
22440
|
+
if (portable === ".." || portable.startsWith(`..${sep6}`) || portable.startsWith("../") || portable === "") {
|
|
21752
22441
|
throw new TypeError(`${field2} must remain inside the project root.`);
|
|
21753
22442
|
}
|
|
21754
22443
|
if (/\r|\n/u.test(portable) || portable.startsWith("-"))
|
|
@@ -21758,11 +22447,11 @@ var projectPath2 = (projectRoot, value, field2, options = {}) => {
|
|
|
21758
22447
|
return portable;
|
|
21759
22448
|
};
|
|
21760
22449
|
var workflowOutputPath = (projectRoot, value) => {
|
|
21761
|
-
const root =
|
|
21762
|
-
const workflows =
|
|
21763
|
-
const path =
|
|
21764
|
-
const portable =
|
|
21765
|
-
if (portable === ".." || portable.startsWith(`..${
|
|
22450
|
+
const root = resolve10(projectRoot);
|
|
22451
|
+
const workflows = resolve10(root, ".github/workflows");
|
|
22452
|
+
const path = resolve10(root, value ?? ".github/workflows/absolute-mobile.yml");
|
|
22453
|
+
const portable = relative9(workflows, path);
|
|
22454
|
+
if (portable === ".." || portable.startsWith(`..${sep6}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
|
|
21766
22455
|
throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
|
|
21767
22456
|
}
|
|
21768
22457
|
return path;
|
|
@@ -22191,12 +22880,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
|
|
|
22191
22880
|
var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
22192
22881
|
const path = workflowOutputPath(options.projectRoot, options.outputPath);
|
|
22193
22882
|
const generated = createAbsoluteMobileGithubWorkflow(options);
|
|
22194
|
-
const previous = await
|
|
22883
|
+
const previous = await exists4(path) ? await readFile16(path, "utf8") : undefined;
|
|
22195
22884
|
if (previous !== undefined && previous !== generated.workflow && !options.force)
|
|
22196
|
-
throw new TypeError(`${
|
|
22885
|
+
throw new TypeError(`${relative9(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
|
|
22197
22886
|
if (previous !== generated.workflow) {
|
|
22198
|
-
await
|
|
22199
|
-
await
|
|
22887
|
+
await mkdir11(dirname10(path), { recursive: true });
|
|
22888
|
+
await writeFile12(path, generated.workflow);
|
|
22200
22889
|
}
|
|
22201
22890
|
return {
|
|
22202
22891
|
changed: previous !== generated.workflow,
|
|
@@ -22209,46 +22898,46 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
|
22209
22898
|
};
|
|
22210
22899
|
|
|
22211
22900
|
// src/mobile/updateSigning.ts
|
|
22212
|
-
import { createHash as
|
|
22901
|
+
import { createHash as createHash5, sign, verify } from "crypto";
|
|
22213
22902
|
import {
|
|
22214
22903
|
cp,
|
|
22215
|
-
mkdir as
|
|
22216
|
-
mkdtemp as
|
|
22217
|
-
readdir as
|
|
22218
|
-
readFile as
|
|
22219
|
-
rename as
|
|
22220
|
-
rm as
|
|
22221
|
-
stat as
|
|
22222
|
-
writeFile as
|
|
22904
|
+
mkdir as mkdir12,
|
|
22905
|
+
mkdtemp as mkdtemp4,
|
|
22906
|
+
readdir as readdir6,
|
|
22907
|
+
readFile as readFile17,
|
|
22908
|
+
rename as rename10,
|
|
22909
|
+
rm as rm7,
|
|
22910
|
+
stat as stat6,
|
|
22911
|
+
writeFile as writeFile13
|
|
22223
22912
|
} from "fs/promises";
|
|
22224
|
-
import { dirname as
|
|
22913
|
+
import { dirname as dirname11, join as join14, relative as relative10, resolve as resolve11 } from "path";
|
|
22225
22914
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
22226
22915
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
22227
|
-
var
|
|
22916
|
+
var sha2563 = (value) => createHash5("sha256").update(value).digest("hex");
|
|
22228
22917
|
var listFiles = async (root, directory = root) => {
|
|
22229
|
-
const entries = await
|
|
22918
|
+
const entries = await readdir6(directory, { withFileTypes: true });
|
|
22230
22919
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
22231
|
-
const path =
|
|
22920
|
+
const path = join14(directory, entry.name);
|
|
22232
22921
|
if (entry.isDirectory())
|
|
22233
22922
|
return listFiles(root, path);
|
|
22234
22923
|
if (!entry.isFile())
|
|
22235
22924
|
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
22236
|
-
return [
|
|
22925
|
+
return [relative10(root, path).replaceAll("\\", "/")];
|
|
22237
22926
|
}));
|
|
22238
22927
|
return paths.flat().sort((left, right) => left.localeCompare(right));
|
|
22239
22928
|
};
|
|
22240
22929
|
var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
22241
|
-
const source =
|
|
22930
|
+
const source = join14(root, path);
|
|
22242
22931
|
const [metadata, contents] = await Promise.all([
|
|
22243
|
-
|
|
22244
|
-
|
|
22932
|
+
stat6(source),
|
|
22933
|
+
readFile17(source)
|
|
22245
22934
|
]);
|
|
22246
|
-
return { bytes: metadata.size, path, sha256:
|
|
22935
|
+
return { bytes: metadata.size, path, sha256: sha2563(contents) };
|
|
22247
22936
|
}));
|
|
22248
|
-
var releaseIdFor = (value) => `amu_${
|
|
22937
|
+
var releaseIdFor = (value) => `amu_${sha2563(canonicalizeAbsoluteMobileUpdate(value))}`;
|
|
22249
22938
|
var buildAbsoluteMobileUpdate = async (options) => {
|
|
22250
|
-
const bundleDirectory =
|
|
22251
|
-
const outputRoot =
|
|
22939
|
+
const bundleDirectory = resolve11(options.bundleDirectory);
|
|
22940
|
+
const outputRoot = resolve11(options.outputDirectory);
|
|
22252
22941
|
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
22253
22942
|
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
22254
22943
|
const paths = await listFiles(bundleDirectory);
|
|
@@ -22276,30 +22965,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
22276
22965
|
value: signature.toString("base64")
|
|
22277
22966
|
}
|
|
22278
22967
|
});
|
|
22279
|
-
await
|
|
22280
|
-
const outputDirectory =
|
|
22281
|
-
const staging = await
|
|
22968
|
+
await mkdir12(outputRoot, { recursive: true });
|
|
22969
|
+
const outputDirectory = join14(outputRoot, manifest.releaseId);
|
|
22970
|
+
const staging = await mkdtemp4(join14(outputRoot, ".stage-"));
|
|
22282
22971
|
try {
|
|
22283
|
-
await cp(bundleDirectory,
|
|
22972
|
+
await cp(bundleDirectory, join14(staging, UPDATE_FILES_DIRECTORY), {
|
|
22284
22973
|
force: true,
|
|
22285
22974
|
recursive: true
|
|
22286
22975
|
});
|
|
22287
|
-
await
|
|
22976
|
+
await writeFile13(join14(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
22288
22977
|
`);
|
|
22289
|
-
await
|
|
22978
|
+
await rename10(staging, outputDirectory);
|
|
22290
22979
|
} catch (error) {
|
|
22291
|
-
await
|
|
22980
|
+
await rm7(staging, { force: true, recursive: true });
|
|
22292
22981
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
22293
22982
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
22294
22983
|
throw error;
|
|
22295
22984
|
}
|
|
22296
22985
|
return {
|
|
22297
22986
|
manifest,
|
|
22298
|
-
manifestPath:
|
|
22987
|
+
manifestPath: join14(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
22299
22988
|
outputDirectory
|
|
22300
22989
|
};
|
|
22301
22990
|
};
|
|
22302
|
-
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await
|
|
22991
|
+
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile17(join14(resolve11(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
22303
22992
|
var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
22304
22993
|
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
22305
22994
|
const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
@@ -22309,8 +22998,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
|
22309
22998
|
};
|
|
22310
22999
|
|
|
22311
23000
|
// src/mobile/expoUpdate.ts
|
|
22312
|
-
import { access as
|
|
22313
|
-
import { dirname as
|
|
23001
|
+
import { access as access11, mkdir as mkdir13, readFile as readFile18, writeFile as writeFile14 } from "fs/promises";
|
|
23002
|
+
import { dirname as dirname12, extname as extname3, join as join15 } from "path";
|
|
22314
23003
|
var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
|
|
22315
23004
|
var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
|
|
22316
23005
|
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -22376,7 +23065,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
|
|
|
22376
23065
|
};
|
|
22377
23066
|
};
|
|
22378
23067
|
var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
22379
|
-
const metadata = JSON.parse(await
|
|
23068
|
+
const metadata = JSON.parse(await readFile18(join15(options.exportDirectory, "metadata.json"), "utf8"));
|
|
22380
23069
|
if (!object2(metadata) || !object2(metadata.fileMetadata))
|
|
22381
23070
|
throw new TypeError("Expo export metadata.json is invalid.");
|
|
22382
23071
|
const descriptor = parseAbsoluteExpoUpdateDescriptor({
|
|
@@ -22387,33 +23076,33 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
|
22387
23076
|
runtimeVersion: options.runtimeVersion
|
|
22388
23077
|
});
|
|
22389
23078
|
const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
|
|
22390
|
-
await Promise.all(referenced.map(({ path }) =>
|
|
23079
|
+
await Promise.all(referenced.map(({ path }) => access11(join15(options.exportDirectory, path)).catch(() => {
|
|
22391
23080
|
throw new TypeError(`Expo export metadata references missing asset ${path}.`);
|
|
22392
23081
|
})));
|
|
22393
|
-
const destination =
|
|
22394
|
-
await
|
|
22395
|
-
await
|
|
23082
|
+
const destination = join15(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
|
|
23083
|
+
await mkdir13(dirname12(destination), { recursive: true });
|
|
23084
|
+
await writeFile14(destination, `${JSON.stringify(descriptor, null, "\t")}
|
|
22396
23085
|
`);
|
|
22397
23086
|
return { descriptor, path: destination };
|
|
22398
23087
|
};
|
|
22399
23088
|
|
|
22400
23089
|
// src/mobile/expoCodeSigning.ts
|
|
22401
23090
|
var import_code_signing_certificates = __toESM(require_main(), 1);
|
|
22402
|
-
import { access as
|
|
22403
|
-
import { dirname as
|
|
22404
|
-
var
|
|
22405
|
-
var
|
|
22406
|
-
const location =
|
|
22407
|
-
return location === "" || location !== ".." && !location.startsWith(`..${
|
|
23091
|
+
import { access as access12, mkdir as mkdir14, writeFile as writeFile15 } from "fs/promises";
|
|
23092
|
+
import { dirname as dirname13, isAbsolute as isAbsolute6, relative as relative11, resolve as resolve12, sep as sep7 } from "path";
|
|
23093
|
+
var exists5 = async (path) => access12(path).then(() => true).catch(() => false);
|
|
23094
|
+
var inside3 = (root, path) => {
|
|
23095
|
+
const location = relative11(root, path);
|
|
23096
|
+
return location === "" || location !== ".." && !location.startsWith(`..${sep7}`) && !isAbsolute6(location);
|
|
22408
23097
|
};
|
|
22409
23098
|
var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
22410
|
-
const root =
|
|
22411
|
-
const certificatePath =
|
|
22412
|
-
const privateKeyPath =
|
|
22413
|
-
const publicKeyPath =
|
|
22414
|
-
if (!
|
|
23099
|
+
const root = resolve12(options.projectRoot);
|
|
23100
|
+
const certificatePath = resolve12(root, options.certificatePath);
|
|
23101
|
+
const privateKeyPath = resolve12(root, options.privateKeyPath);
|
|
23102
|
+
const publicKeyPath = resolve12(root, options.publicKeyPath ?? resolve12(dirname13(privateKeyPath), "public-key.pem"));
|
|
23103
|
+
if (!inside3(root, certificatePath))
|
|
22415
23104
|
throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
|
|
22416
|
-
if (
|
|
23105
|
+
if (inside3(root, privateKeyPath) || inside3(root, publicKeyPath))
|
|
22417
23106
|
throw new TypeError("Expo code-signing private/public key output must remain outside the project and source control.");
|
|
22418
23107
|
const commonName = options.commonName.trim();
|
|
22419
23108
|
if (!commonName || commonName.length > 64)
|
|
@@ -22422,7 +23111,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
22422
23111
|
if (!Number.isSafeInteger(validityYears) || validityYears < 1 || validityYears > 20)
|
|
22423
23112
|
throw new TypeError("Expo code-signing validity must be an integer from 1 through 20 years.");
|
|
22424
23113
|
const collisions = (await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map(async (path) => ({
|
|
22425
|
-
exists: await
|
|
23114
|
+
exists: await exists5(path),
|
|
22426
23115
|
path
|
|
22427
23116
|
})))).filter((entry) => entry.exists);
|
|
22428
23117
|
if (collisions.length > 0)
|
|
@@ -22440,11 +23129,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
22440
23129
|
import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
|
|
22441
23130
|
const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
|
|
22442
23131
|
const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
|
|
22443
|
-
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) =>
|
|
23132
|
+
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir14(dirname13(path), { recursive: true })));
|
|
22444
23133
|
await Promise.all([
|
|
22445
|
-
|
|
22446
|
-
|
|
22447
|
-
|
|
23134
|
+
writeFile15(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
|
|
23135
|
+
writeFile15(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
|
|
23136
|
+
writeFile15(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
|
|
22448
23137
|
]);
|
|
22449
23138
|
return {
|
|
22450
23139
|
certificatePath,
|
|
@@ -22455,8 +23144,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
22455
23144
|
};
|
|
22456
23145
|
|
|
22457
23146
|
// src/mobile/updatePublisher.ts
|
|
22458
|
-
import { access as
|
|
22459
|
-
import { isAbsolute as
|
|
23147
|
+
import { access as access13 } from "fs/promises";
|
|
23148
|
+
import { isAbsolute as isAbsolute7, relative as relative12, resolve as resolve13, sep as sep8 } from "path";
|
|
22460
23149
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
22461
23150
|
var lifecycleMethod = (publisher, name) => {
|
|
22462
23151
|
const method = publisher[name];
|
|
@@ -22565,16 +23254,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
|
|
|
22565
23254
|
var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22566
23255
|
var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
|
|
22567
23256
|
var projectPath3 = (projectRoot, requested, label) => {
|
|
22568
|
-
const root =
|
|
22569
|
-
const path =
|
|
22570
|
-
const projectRelative =
|
|
22571
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
23257
|
+
const root = resolve13(projectRoot);
|
|
23258
|
+
const path = resolve13(root, requested);
|
|
23259
|
+
const projectRelative = relative12(root, path);
|
|
23260
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep8}`) || isAbsolute7(projectRelative))
|
|
22572
23261
|
throw new TypeError(`${label} must remain inside the project.`);
|
|
22573
23262
|
return path;
|
|
22574
23263
|
};
|
|
22575
23264
|
var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
22576
23265
|
const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
|
|
22577
|
-
await
|
|
23266
|
+
await access13(modulePath).catch(() => {
|
|
22578
23267
|
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
22579
23268
|
});
|
|
22580
23269
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
@@ -22672,7 +23361,7 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
|
|
|
22672
23361
|
];
|
|
22673
23362
|
var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
|
|
22674
23363
|
var directProjectPackages = async (projectRoot) => {
|
|
22675
|
-
const manifest = JSON.parse(await
|
|
23364
|
+
const manifest = JSON.parse(await readFile19(join16(projectRoot, "package.json"), "utf8"));
|
|
22676
23365
|
if (!isRecord7(manifest))
|
|
22677
23366
|
throw new TypeError("Application package.json must contain an object.");
|
|
22678
23367
|
const names = new Set;
|
|
@@ -22686,7 +23375,7 @@ var directProjectPackages = async (projectRoot) => {
|
|
|
22686
23375
|
};
|
|
22687
23376
|
var resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
22688
23377
|
try {
|
|
22689
|
-
const manifest = JSON.parse(await
|
|
23378
|
+
const manifest = JSON.parse(await readFile19(join16(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
22690
23379
|
return isRecord7(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
22691
23380
|
} catch {
|
|
22692
23381
|
return;
|
|
@@ -22736,18 +23425,8 @@ var requireMobileConfig = (value) => {
|
|
|
22736
23425
|
}
|
|
22737
23426
|
return value;
|
|
22738
23427
|
};
|
|
22739
|
-
var capacitorExecutable = async (projectRoot) => {
|
|
22740
|
-
const executable = join14(projectRoot, "node_modules", ".bin", "cap");
|
|
22741
|
-
try {
|
|
22742
|
-
await access13(executable);
|
|
22743
|
-
return executable;
|
|
22744
|
-
} catch {
|
|
22745
|
-
throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
|
|
22746
|
-
}
|
|
22747
|
-
};
|
|
22748
23428
|
var runCapacitor = async (projectRoot, args) => {
|
|
22749
|
-
const
|
|
22750
|
-
const process2 = Bun.spawn([executable, ...args], {
|
|
23429
|
+
const process2 = Bun.spawn(["bun", "x", "--no-install", "cap", ...args], {
|
|
22751
23430
|
cwd: projectRoot,
|
|
22752
23431
|
stderr: "inherit",
|
|
22753
23432
|
stdin: "inherit",
|
|
@@ -22755,14 +23434,14 @@ var runCapacitor = async (projectRoot, args) => {
|
|
|
22755
23434
|
});
|
|
22756
23435
|
const exitCode = await process2.exited;
|
|
22757
23436
|
if (exitCode !== 0) {
|
|
22758
|
-
throw new TypeError(`Capacitor exited with status ${exitCode}
|
|
23437
|
+
throw new TypeError(`Capacitor exited with status ${exitCode}. Ensure this app directly depends on: ${CAPACITOR_PACKAGES.join(" ")}`);
|
|
22759
23438
|
}
|
|
22760
23439
|
};
|
|
22761
23440
|
var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
|
|
22762
23441
|
var expoExecutable = async (project) => {
|
|
22763
|
-
const executable =
|
|
23442
|
+
const executable = join16(project, "node_modules", ".bin", "expo");
|
|
22764
23443
|
try {
|
|
22765
|
-
await
|
|
23444
|
+
await access14(executable);
|
|
22766
23445
|
return executable;
|
|
22767
23446
|
} catch {
|
|
22768
23447
|
throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
|
|
@@ -22794,14 +23473,14 @@ var runExpo = async (project, args, options = {}) => {
|
|
|
22794
23473
|
};
|
|
22795
23474
|
var ensureExpoPackages = async (project, args) => {
|
|
22796
23475
|
try {
|
|
22797
|
-
const manifest = JSON.parse(await
|
|
23476
|
+
const manifest = JSON.parse(await readFile19(join16(project, "package.json"), "utf8"));
|
|
22798
23477
|
const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
|
|
22799
23478
|
if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
|
|
22800
23479
|
throw new TypeError("Generated Expo dependencies are invalid.");
|
|
22801
23480
|
await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
|
|
22802
23481
|
if (typeof expected !== "string")
|
|
22803
23482
|
throw new TypeError("Generated Expo dependency version is invalid.");
|
|
22804
|
-
const installed = JSON.parse(await
|
|
23483
|
+
const installed = JSON.parse(await readFile19(join16(project, "node_modules", name, "package.json"), "utf8"));
|
|
22805
23484
|
const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
|
|
22806
23485
|
if (typeof actual !== "string")
|
|
22807
23486
|
throw new TypeError("Installed Expo dependency version is invalid.");
|
|
@@ -22959,6 +23638,7 @@ var initialize = async (args) => {
|
|
|
22959
23638
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile);
|
|
22960
23639
|
await applyAbsoluteNativeUpdates(mobile);
|
|
22961
23640
|
await applyAbsoluteNativeObservability(mobile);
|
|
23641
|
+
await applyAbsoluteNativeReleaseReadiness(mobile);
|
|
22962
23642
|
};
|
|
22963
23643
|
var sync = async (args) => {
|
|
22964
23644
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
@@ -22994,10 +23674,11 @@ var sync = async (args) => {
|
|
|
22994
23674
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, platforms);
|
|
22995
23675
|
await applyAbsoluteNativeUpdates(mobile, platforms);
|
|
22996
23676
|
await applyAbsoluteNativeObservability(mobile, platforms);
|
|
23677
|
+
await applyAbsoluteNativeReleaseReadiness(mobile, platforms);
|
|
22997
23678
|
};
|
|
22998
23679
|
var associations = async (args) => {
|
|
22999
23680
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
23000
|
-
const outputDirectory =
|
|
23681
|
+
const outputDirectory = resolve14(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
23001
23682
|
if (args.includes("--verify")) {
|
|
23002
23683
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
23003
23684
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -23049,7 +23730,7 @@ var generateGithubCi = async (args) => {
|
|
|
23049
23730
|
const publicResult = {
|
|
23050
23731
|
changed: result.changed,
|
|
23051
23732
|
format: result.format,
|
|
23052
|
-
path:
|
|
23733
|
+
path: relative13(projectRoot, result.path).replaceAll("\\", "/"),
|
|
23053
23734
|
platforms: result.platforms,
|
|
23054
23735
|
publishing: result.publishing,
|
|
23055
23736
|
requiredSecrets: result.requiredSecrets
|
|
@@ -23160,9 +23841,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
23160
23841
|
temporaryDirectory: undefined
|
|
23161
23842
|
};
|
|
23162
23843
|
await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
|
|
23163
|
-
const temporaryParent =
|
|
23164
|
-
await
|
|
23165
|
-
const temporaryDirectory = await
|
|
23844
|
+
const temporaryParent = join16(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
|
|
23845
|
+
await mkdir15(temporaryParent, { recursive: true });
|
|
23846
|
+
const temporaryDirectory = await mkdtemp5(join16(temporaryParent, ".stage-"));
|
|
23166
23847
|
try {
|
|
23167
23848
|
await runExpo(mobile.nativeProjectDirectory, [
|
|
23168
23849
|
"export",
|
|
@@ -23174,7 +23855,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
23174
23855
|
temporaryDirectory,
|
|
23175
23856
|
"--clear"
|
|
23176
23857
|
], { production: true });
|
|
23177
|
-
const generatedApp = JSON.parse(await
|
|
23858
|
+
const generatedApp = JSON.parse(await readFile19(join16(mobile.nativeProjectDirectory, "app.json"), "utf8"));
|
|
23178
23859
|
const expoConfig = isRecord7(generatedApp) ? generatedApp.expo : undefined;
|
|
23179
23860
|
if (!isRecord7(expoConfig))
|
|
23180
23861
|
throw new TypeError("Generated Expo app configuration is invalid.");
|
|
@@ -23192,7 +23873,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
23192
23873
|
temporaryDirectory
|
|
23193
23874
|
};
|
|
23194
23875
|
} catch (error) {
|
|
23195
|
-
await
|
|
23876
|
+
await rm8(temporaryDirectory, { force: true, recursive: true });
|
|
23196
23877
|
throw error;
|
|
23197
23878
|
}
|
|
23198
23879
|
};
|
|
@@ -23217,11 +23898,11 @@ var buildMobileUpdate = async (args) => {
|
|
|
23217
23898
|
let expoExportDirectory;
|
|
23218
23899
|
try {
|
|
23219
23900
|
await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
|
|
23220
|
-
const embedded = JSON.parse(await
|
|
23901
|
+
const embedded = JSON.parse(await readFile19(join16(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
|
|
23221
23902
|
const runtimeFingerprint = isRecord7(embedded) ? embedded.nativeRuntime : undefined;
|
|
23222
23903
|
if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
|
|
23223
23904
|
throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
|
|
23224
|
-
const privateKey = await
|
|
23905
|
+
const privateKey = await readFile19(resolve14(projectRoot, signingKeyPath));
|
|
23225
23906
|
const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
|
|
23226
23907
|
const derivedPublicKey = createPublicKey2(privateKey).export({
|
|
23227
23908
|
format: "der",
|
|
@@ -23242,7 +23923,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
23242
23923
|
channel: mobile.updates.channel,
|
|
23243
23924
|
classification,
|
|
23244
23925
|
keyId,
|
|
23245
|
-
outputDirectory: valueAfter(args, "--outdir") ??
|
|
23926
|
+
outputDirectory: valueAfter(args, "--outdir") ?? join16(projectRoot, ".absolutejs", "mobile", "updates"),
|
|
23246
23927
|
privateKey,
|
|
23247
23928
|
runtimeFingerprint
|
|
23248
23929
|
});
|
|
@@ -23254,7 +23935,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
23254
23935
|
return result;
|
|
23255
23936
|
} finally {
|
|
23256
23937
|
if (expoExportDirectory)
|
|
23257
|
-
await
|
|
23938
|
+
await rm8(expoExportDirectory, { force: true, recursive: true });
|
|
23258
23939
|
sendTelemetryEvent("mobile:update-build", {
|
|
23259
23940
|
classification: classification ?? "invalid",
|
|
23260
23941
|
durationMs: Math.round(performance.now() - startedAt),
|
|
@@ -23284,7 +23965,7 @@ var generateExpoUpdateSigning = async (args) => {
|
|
|
23284
23965
|
...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
|
|
23285
23966
|
...validityYears === undefined ? {} : { validityYears }
|
|
23286
23967
|
});
|
|
23287
|
-
const certificatePath =
|
|
23968
|
+
const certificatePath = relative13(projectRoot, result.certificatePath).replaceAll("\\", "/");
|
|
23288
23969
|
sendTelemetryEvent("mobile:update-code-signing-generated", {
|
|
23289
23970
|
engine: "expo",
|
|
23290
23971
|
validityYears: validityYears ?? 10
|
|
@@ -23346,7 +24027,7 @@ var provisionMobileUpdate = async (args) => {
|
|
|
23346
24027
|
publicKeys: mobile.updates.publicKeys,
|
|
23347
24028
|
storage: requestedStorage
|
|
23348
24029
|
});
|
|
23349
|
-
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${
|
|
24030
|
+
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative13(projectRoot, path).replaceAll("\\", "/")}.`);
|
|
23350
24031
|
if (requestedStorage === "local")
|
|
23351
24032
|
console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
|
|
23352
24033
|
else
|
|
@@ -23758,6 +24439,7 @@ var prepareCapacitorIosReleaseProject = async (mobile, projectRoot) => {
|
|
|
23758
24439
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, ["ios"]);
|
|
23759
24440
|
await applyAbsoluteNativeUpdates(mobile, ["ios"]);
|
|
23760
24441
|
await applyAbsoluteNativeObservability(mobile, ["ios"]);
|
|
24442
|
+
await applyAbsoluteNativeReleaseReadiness(mobile, ["ios"]);
|
|
23761
24443
|
};
|
|
23762
24444
|
var prepareIosReleaseProject = (mobile, projectRoot, args) => mobile.engine === "expo" ? prepareExpoIosReleaseProject(mobile, projectRoot, args) : prepareCapacitorIosReleaseProject(mobile, projectRoot);
|
|
23763
24445
|
var prepareCapacitorAndroidReleaseProject = async (mobile, projectRoot) => {
|
|
@@ -23770,6 +24452,7 @@ var prepareCapacitorAndroidReleaseProject = async (mobile, projectRoot) => {
|
|
|
23770
24452
|
await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, ["android"]);
|
|
23771
24453
|
await applyAbsoluteNativeUpdates(mobile, ["android"]);
|
|
23772
24454
|
await applyAbsoluteNativeObservability(mobile, ["android"]);
|
|
24455
|
+
await applyAbsoluteNativeReleaseReadiness(mobile, ["android"]);
|
|
23773
24456
|
};
|
|
23774
24457
|
var prepareAndroidReleaseProject = (mobile, projectRoot, args) => mobile.engine === "expo" ? prepareExpoAndroidReleaseProject(mobile, projectRoot, args) : prepareCapacitorAndroidReleaseProject(mobile, projectRoot);
|
|
23775
24458
|
var buildAndroid = async (args, prepareVersionCode) => {
|
|
@@ -23821,7 +24504,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
|
|
|
23821
24504
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
23822
24505
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
23823
24506
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
23824
|
-
console.log(`Metadata: ${
|
|
24507
|
+
console.log(`Metadata: ${join16(release.releaseRoot, "release.json")}`);
|
|
23825
24508
|
return release;
|
|
23826
24509
|
} finally {
|
|
23827
24510
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -23909,6 +24592,7 @@ var buildLocalIosRelease = async (options) => {
|
|
|
23909
24592
|
developmentTeam: process.env.ABSOLUTE_IOS_DEVELOPMENT_TEAM,
|
|
23910
24593
|
...options.mobile.engine === "expo" ? { env: expoProductionEnvironment() } : {},
|
|
23911
24594
|
outputDirectory: valueAfter(options.args, "--outdir"),
|
|
24595
|
+
registeredDeviceArtifact: options.args.includes("--registered-device-artifact"),
|
|
23912
24596
|
...options.prepareBuildNumber === undefined ? {} : { prepareBuildNumber: options.prepareBuildNumber },
|
|
23913
24597
|
projectRoot: options.projectRoot
|
|
23914
24598
|
});
|
|
@@ -23939,6 +24623,8 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
23939
24623
|
const remoteProfile = requestedRemote !== undefined || process.platform !== "darwin" ? await getAbsoluteRemoteMacProfile(requestedRemote, remoteProfilePath()) : undefined;
|
|
23940
24624
|
if (process.platform !== "darwin" && !remoteProfile)
|
|
23941
24625
|
throw new TypeError("iOS release builds require macOS or a paired Remote Mac. Run `absolute mobile pair mac <name> <user@host>`.");
|
|
24626
|
+
if (remoteProfile && args.includes("--registered-device-artifact"))
|
|
24627
|
+
throw new TypeError("--registered-device-artifact currently requires a local Mac; paired Remote Mac transfer support is not yet available.");
|
|
23942
24628
|
let success = false;
|
|
23943
24629
|
const cancellation = remoteProfile ? new AbortController : undefined;
|
|
23944
24630
|
let stopListeningForCancellation = () => {
|
|
@@ -23977,7 +24663,9 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
23977
24663
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
23978
24664
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} iOS IPA ${release.metadata.marketingVersion}${release.metadata.buildNumber ? ` (${release.metadata.buildNumber})` : ""} in ${getDurationString(durationMs)}.`);
|
|
23979
24665
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
23980
|
-
|
|
24666
|
+
if (release.registeredArtifactPath)
|
|
24667
|
+
console.log(`Registered-device artifact: ${release.registeredArtifactPath}`);
|
|
24668
|
+
console.log(`Metadata: ${join16(release.releaseRoot, "release.json")}`);
|
|
23981
24669
|
return release;
|
|
23982
24670
|
} finally {
|
|
23983
24671
|
stopListeningForCancellation();
|
|
@@ -24089,7 +24777,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
24089
24777
|
checks.push({
|
|
24090
24778
|
id: "sync.storage-schema",
|
|
24091
24779
|
label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
|
|
24092
|
-
path:
|
|
24780
|
+
path: join16(projectRoot, "package.json"),
|
|
24093
24781
|
platform: "host",
|
|
24094
24782
|
status: "pass"
|
|
24095
24783
|
});
|
|
@@ -24097,7 +24785,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
24097
24785
|
checks.push({
|
|
24098
24786
|
id: "sync.storage-schema",
|
|
24099
24787
|
label: "Offline schema metadata is invalid",
|
|
24100
|
-
path:
|
|
24788
|
+
path: join16(projectRoot, "package.json"),
|
|
24101
24789
|
platform: "host",
|
|
24102
24790
|
remediation: error instanceof Error ? error.message : String(error),
|
|
24103
24791
|
status: "fail"
|
|
@@ -24164,7 +24852,7 @@ AbsoluteJS can configure ${installPlatform} emulation:`);
|
|
|
24164
24852
|
Emulator setup verification:`);
|
|
24165
24853
|
printDoctorChecks(result.checks.filter((check) => check.platform === "host" || check.platform === installPlatform));
|
|
24166
24854
|
};
|
|
24167
|
-
var
|
|
24855
|
+
var captureCommand4 = (command) => {
|
|
24168
24856
|
const result = Bun.spawnSync(command, {
|
|
24169
24857
|
stderr: "pipe",
|
|
24170
24858
|
stdin: "ignore",
|
|
@@ -24185,7 +24873,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
|
|
|
24185
24873
|
}
|
|
24186
24874
|
return { https: args.includes("--https"), port };
|
|
24187
24875
|
}
|
|
24188
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
24876
|
+
const instances = listLiveInstances().filter((instance2) => resolve14(instance2.cwd) === resolve14(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
24189
24877
|
if (instances.length !== 1) {
|
|
24190
24878
|
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>.");
|
|
24191
24879
|
}
|
|
@@ -24214,7 +24902,7 @@ var requireAndroidAdb = async () => {
|
|
|
24214
24902
|
return adb2;
|
|
24215
24903
|
};
|
|
24216
24904
|
var selectAndroidSerial = (adb2, explicitSerial) => {
|
|
24217
|
-
const devices =
|
|
24905
|
+
const devices = captureCommand4([adb2, "devices"]);
|
|
24218
24906
|
if (devices.exitCode !== 0) {
|
|
24219
24907
|
throw new Error(`Could not list Android targets: ${devices.stderr.trim() || devices.stdout.trim()}`);
|
|
24220
24908
|
}
|
|
@@ -24234,7 +24922,7 @@ var selectAndroidSerial = (adb2, explicitSerial) => {
|
|
|
24234
24922
|
var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial, json) => {
|
|
24235
24923
|
if (explicitSerial)
|
|
24236
24924
|
return selectAndroidSerial(adb2, explicitSerial);
|
|
24237
|
-
const current =
|
|
24925
|
+
const current = captureCommand4([adb2, "devices"]);
|
|
24238
24926
|
const ready = current.exitCode === 0 ? parseAdbDevices(current.stdout).find((serial) => serial.startsWith("emulator-")) : undefined;
|
|
24239
24927
|
if (ready)
|
|
24240
24928
|
return ready;
|
|
@@ -24253,11 +24941,11 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
|
|
|
24253
24941
|
launched.unref();
|
|
24254
24942
|
const startedAt = performance.now();
|
|
24255
24943
|
const bootedSerial = () => {
|
|
24256
|
-
const devices =
|
|
24944
|
+
const devices = captureCommand4([adb2, "devices"]);
|
|
24257
24945
|
const serial = devices.exitCode === 0 ? parseAdbDevices(devices.stdout).find((candidate) => candidate.startsWith("emulator-")) : undefined;
|
|
24258
24946
|
if (!serial)
|
|
24259
24947
|
return;
|
|
24260
|
-
const booted =
|
|
24948
|
+
const booted = captureCommand4([
|
|
24261
24949
|
adb2,
|
|
24262
24950
|
"-s",
|
|
24263
24951
|
serial,
|
|
@@ -24279,8 +24967,8 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
|
|
|
24279
24967
|
return waitForBoot();
|
|
24280
24968
|
};
|
|
24281
24969
|
var safeArtifactRoot = (projectRoot, value) => {
|
|
24282
|
-
const root =
|
|
24283
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
24970
|
+
const root = resolve14(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
24971
|
+
if (root !== projectRoot && !root.startsWith(`${resolve14(projectRoot)}/`)) {
|
|
24284
24972
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
24285
24973
|
}
|
|
24286
24974
|
return root;
|
|
@@ -24308,12 +24996,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
|
|
|
24308
24996
|
});
|
|
24309
24997
|
};
|
|
24310
24998
|
var writeAndroidFailureArtifacts = async (options) => {
|
|
24311
|
-
await
|
|
24312
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
24999
|
+
await mkdir15(options.artifactRoot, { recursive: true });
|
|
25000
|
+
const screenshot = options.session ? await options.session.screenshot(join16(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
24313
25001
|
return;
|
|
24314
25002
|
}) : undefined;
|
|
24315
|
-
const diagnosticPath =
|
|
24316
|
-
await
|
|
25003
|
+
const diagnosticPath = join16(options.artifactRoot, "android-failure.json");
|
|
25004
|
+
await writeFile16(diagnosticPath, `${JSON.stringify({
|
|
24317
25005
|
diagnostics: options.session?.diagnostics ?? [],
|
|
24318
25006
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
24319
25007
|
platform: "android",
|
|
@@ -24334,7 +25022,7 @@ var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
|
|
|
24334
25022
|
]);
|
|
24335
25023
|
if (exitCode !== 0)
|
|
24336
25024
|
throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
|
|
24337
|
-
await
|
|
25025
|
+
await writeFile16(destination, bytes);
|
|
24338
25026
|
return destination;
|
|
24339
25027
|
};
|
|
24340
25028
|
var printAndroidReleaseAcceptance = (result, json) => {
|
|
@@ -24351,8 +25039,6 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
24351
25039
|
throw new TypeError("mobile test android --release requires a release directory or release.json path.");
|
|
24352
25040
|
if (!mobile.platforms.includes("android"))
|
|
24353
25041
|
throw new TypeError("mobile test android requires android in mobile.platforms.");
|
|
24354
|
-
if (mobile.engine !== "expo")
|
|
24355
|
-
throw new TypeError("mobile test android --release currently requires mobile.engine: expo; use the ordinary test command for Capacitor.");
|
|
24356
25042
|
const release = await readAbsoluteAndroidRelease(projectRoot, requested);
|
|
24357
25043
|
if (release.metadata.appId !== mobile.appId)
|
|
24358
25044
|
throw new TypeError(`Android release app ID ${release.metadata.appId} does not match mobile.appId ${mobile.appId}.`);
|
|
@@ -24390,7 +25076,7 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
24390
25076
|
release,
|
|
24391
25077
|
serial
|
|
24392
25078
|
});
|
|
24393
|
-
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial,
|
|
25079
|
+
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join16(reportRoot, "android-release.png")) : undefined;
|
|
24394
25080
|
sendTelemetryEvent("mobile:android-release-conformance", {
|
|
24395
25081
|
durationMs: result.durationMs,
|
|
24396
25082
|
engine: result.engine,
|
|
@@ -24437,9 +25123,9 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
24437
25123
|
platform: "android",
|
|
24438
25124
|
success: false
|
|
24439
25125
|
});
|
|
24440
|
-
await
|
|
24441
|
-
const diagnosticPath =
|
|
24442
|
-
await
|
|
25126
|
+
await mkdir15(artifactRoot, { recursive: true });
|
|
25127
|
+
const diagnosticPath = join16(artifactRoot, "android-release-failure.json");
|
|
25128
|
+
await writeFile16(diagnosticPath, `${JSON.stringify({
|
|
24443
25129
|
error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
|
|
24444
25130
|
platform: "android",
|
|
24445
25131
|
provider: mobile.engine,
|
|
@@ -24533,7 +25219,7 @@ var testAndroid = async (args) => {
|
|
|
24533
25219
|
console.log(JSON.stringify(report, null, 2));
|
|
24534
25220
|
else
|
|
24535
25221
|
printAndroidTestReport(report);
|
|
24536
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
25222
|
+
const screenshot = reportRoot ? await session.screenshot(join16(artifactRoot, "android-emulator.png")) : undefined;
|
|
24537
25223
|
await writeRequestedAndroidReport({
|
|
24538
25224
|
adb: adb2,
|
|
24539
25225
|
args,
|
|
@@ -24602,14 +25288,14 @@ var requireIosTestContext = (args, projectRoot) => {
|
|
|
24602
25288
|
const port = Number(explicit);
|
|
24603
25289
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
24604
25290
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
24605
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
25291
|
+
const instance2 = listLiveInstances().find((candidate) => resolve14(candidate.cwd) === resolve14(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
24606
25292
|
return {
|
|
24607
25293
|
https: instance2?.https ?? args.includes("--https"),
|
|
24608
25294
|
instance: instance2,
|
|
24609
25295
|
port
|
|
24610
25296
|
};
|
|
24611
25297
|
}
|
|
24612
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
25298
|
+
const instances = listLiveInstances().filter((instance2) => resolve14(instance2.cwd) === resolve14(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
24613
25299
|
if (instances.length !== 1)
|
|
24614
25300
|
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>.");
|
|
24615
25301
|
const [instance] = instances;
|
|
@@ -24699,11 +25385,11 @@ var physicalIosCapture = async (options) => {
|
|
|
24699
25385
|
remote: false,
|
|
24700
25386
|
xcodeVersion: requireCapturedCommand(["/usr/bin/xcodebuild", "-version"], "Xcode version inspection").stdout.trim(),
|
|
24701
25387
|
xcrun: "/usr/bin/xcrun",
|
|
24702
|
-
capture: async (command) =>
|
|
25388
|
+
capture: async (command) => captureCommand4(command)
|
|
24703
25389
|
};
|
|
24704
25390
|
};
|
|
24705
25391
|
var selectIosSimulator = (xcrun, explicitUdid) => {
|
|
24706
|
-
const result =
|
|
25392
|
+
const result = captureCommand4([
|
|
24707
25393
|
xcrun,
|
|
24708
25394
|
"simctl",
|
|
24709
25395
|
"list",
|
|
@@ -24725,16 +25411,37 @@ var selectIosSimulator = (xcrun, explicitUdid) => {
|
|
|
24725
25411
|
throw new TypeError("No ready AbsoluteJS iOS simulator was found. Start `bun dev` and wait for the iOS target to become ready.");
|
|
24726
25412
|
return selected;
|
|
24727
25413
|
};
|
|
25414
|
+
var selectOrStartIosReleaseSimulator = async (xcrun, explicitUdid) => {
|
|
25415
|
+
const result = captureCommand4([
|
|
25416
|
+
xcrun,
|
|
25417
|
+
"simctl",
|
|
25418
|
+
"list",
|
|
25419
|
+
"devices",
|
|
25420
|
+
"available",
|
|
25421
|
+
"-j"
|
|
25422
|
+
]);
|
|
25423
|
+
if (result.exitCode !== 0)
|
|
25424
|
+
throw new Error(`Could not list iOS simulators: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
25425
|
+
const devices = parseIosSimulators(result.stdout).filter((device) => device.isAvailable);
|
|
25426
|
+
const selected = explicitUdid ? devices.find((device) => device.udid === explicitUdid) : devices.find((device) => device.name === ABSOLUTE_IOS_SIMULATOR_NAME);
|
|
25427
|
+
if (!selected)
|
|
25428
|
+
throw new TypeError(explicitUdid ? `iOS simulator ${explicitUdid} is unavailable.` : "No managed AbsoluteJS iOS simulator was found. Run `absolute mobile doctor ios --fix`.");
|
|
25429
|
+
if (selected.state !== "Booted") {
|
|
25430
|
+
requireCapturedCommand([xcrun, "simctl", "boot", selected.udid], "iOS Simulator boot");
|
|
25431
|
+
requireCapturedCommand([xcrun, "simctl", "bootstatus", selected.udid, "-b"], "iOS Simulator readiness");
|
|
25432
|
+
}
|
|
25433
|
+
return { ...selected, state: "Booted" };
|
|
25434
|
+
};
|
|
24728
25435
|
var requireCapturedCommand = (command, label) => {
|
|
24729
|
-
const result =
|
|
25436
|
+
const result = captureCommand4(command);
|
|
24730
25437
|
if (result.exitCode !== 0)
|
|
24731
25438
|
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
|
|
24732
25439
|
return result;
|
|
24733
25440
|
};
|
|
24734
25441
|
var writeIosFailureArtifacts = async (options) => {
|
|
24735
|
-
await
|
|
24736
|
-
const screenshot =
|
|
24737
|
-
const screenshotResult =
|
|
25442
|
+
await mkdir15(options.artifactRoot, { recursive: true });
|
|
25443
|
+
const screenshot = join16(options.artifactRoot, "ios-failure.png");
|
|
25444
|
+
const screenshotResult = captureCommand4([
|
|
24738
25445
|
options.xcrun,
|
|
24739
25446
|
"simctl",
|
|
24740
25447
|
"io",
|
|
@@ -24742,8 +25449,8 @@ var writeIosFailureArtifacts = async (options) => {
|
|
|
24742
25449
|
"screenshot",
|
|
24743
25450
|
screenshot
|
|
24744
25451
|
]);
|
|
24745
|
-
const diagnosticPath =
|
|
24746
|
-
await
|
|
25452
|
+
const diagnosticPath = join16(options.artifactRoot, "ios-failure.json");
|
|
25453
|
+
await writeFile16(diagnosticPath, `${JSON.stringify({
|
|
24747
25454
|
appId: options.appId,
|
|
24748
25455
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
24749
25456
|
platform: "ios",
|
|
@@ -24770,8 +25477,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
|
|
|
24770
25477
|
var absolutejsVersionForReport = async () => {
|
|
24771
25478
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
24772
25479
|
const versions = await Promise.all([
|
|
24773
|
-
|
|
24774
|
-
|
|
25480
|
+
resolve14(import.meta.dir, "..", "..", "package.json"),
|
|
25481
|
+
resolve14(import.meta.dir, "..", "..", "..", "package.json")
|
|
24775
25482
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
24776
25483
|
for (const version of versions) {
|
|
24777
25484
|
if (version === "unknown")
|
|
@@ -24818,7 +25525,11 @@ var writeRequestedIosReport = async (options) => {
|
|
|
24818
25525
|
const metadata = options.metadata ?? (options.xcrun ? await iosReportMetadata(options.xcrun) : undefined);
|
|
24819
25526
|
if (!metadata)
|
|
24820
25527
|
throw new Error("iOS report metadata could not be inspected.");
|
|
24821
|
-
const paths = await writeAbsoluteIosPartnerReport(reportRoot, createAbsoluteIosPartnerReport({
|
|
25528
|
+
const paths = await writeAbsoluteIosPartnerReport(reportRoot, createAbsoluteIosPartnerReport({
|
|
25529
|
+
...metadata,
|
|
25530
|
+
...options.provider ? { provider: options.provider } : {},
|
|
25531
|
+
run: options.run
|
|
25532
|
+
}));
|
|
24822
25533
|
const print = options.args.includes("--json") ? console.error : console.log;
|
|
24823
25534
|
print(`iOS partner report: ${paths.markdownPath}`);
|
|
24824
25535
|
print(`Return this report directory: ${paths.directory}`);
|
|
@@ -24935,8 +25646,167 @@ var testPhysicalIos = async (options) => {
|
|
|
24935
25646
|
throw error;
|
|
24936
25647
|
}
|
|
24937
25648
|
};
|
|
25649
|
+
var printIosReleaseAcceptance = (result, json) => {
|
|
25650
|
+
if (json) {
|
|
25651
|
+
console.log(JSON.stringify(result, null, 2));
|
|
25652
|
+
return;
|
|
25653
|
+
}
|
|
25654
|
+
console.log(`\u2713 ${result.artifactExactness} ${result.engine} iOS release evidence passed via ${result.distribution}.`);
|
|
25655
|
+
console.log(`\u2713 Embedded local content launched in ${getDurationString(result.launchMs)} and relaunched in ${getDurationString(result.relaunchMs)}.`);
|
|
25656
|
+
console.log(result.networkUnavailable === "user-confirmed" ? "\u2713 Physical-device network-unavailable state was explicitly confirmed." : "\u2139 Simulator evidence does not claim physical-device offline behavior.");
|
|
25657
|
+
};
|
|
25658
|
+
var iosReleaseRun = (result, appId, targetId) => ({
|
|
25659
|
+
appId,
|
|
25660
|
+
durationMs: result.durationMs,
|
|
25661
|
+
hmrConnected: false,
|
|
25662
|
+
iosRelease: {
|
|
25663
|
+
artifactBytes: result.artifactBytes,
|
|
25664
|
+
artifactExactness: result.artifactExactness,
|
|
25665
|
+
artifactSha256: result.artifactSha256,
|
|
25666
|
+
distribution: result.distribution,
|
|
25667
|
+
embeddedLocal: result.embeddedLocal,
|
|
25668
|
+
engine: result.engine,
|
|
25669
|
+
installMs: result.installMs,
|
|
25670
|
+
launchMs: result.launchMs,
|
|
25671
|
+
networkUnavailable: result.networkUnavailable,
|
|
25672
|
+
relaunchMs: result.relaunchMs,
|
|
25673
|
+
releaseId: result.releaseId,
|
|
25674
|
+
signed: result.signed
|
|
25675
|
+
},
|
|
25676
|
+
status: "pass",
|
|
25677
|
+
targetId,
|
|
25678
|
+
targetKind: result.target
|
|
25679
|
+
});
|
|
25680
|
+
var runIosPhysicalReleaseTarget = async (options) => {
|
|
25681
|
+
const confirmed = options.args.includes("--yes") || await confirmInstall("On the selected iPhone, enable Airplane Mode and then disable Wi-Fi in Settings. Confirm both are still disabled and continue with two release launches?");
|
|
25682
|
+
if (!confirmed)
|
|
25683
|
+
throw new TypeError("Physical iOS offline acceptance was cancelled because network-unavailable state was not confirmed.");
|
|
25684
|
+
const targetId = normalizeAbsoluteIosDeviceIdentifier(options.device);
|
|
25685
|
+
const result = await runAbsoluteIosDeviceReleaseAcceptance({
|
|
25686
|
+
device: targetId,
|
|
25687
|
+
distribution: options.args.includes("--testflight") ? "testflight" : "registered-device",
|
|
25688
|
+
networkUnavailableConfirmed: true,
|
|
25689
|
+
release: options.release,
|
|
25690
|
+
xcrun: options.xcrun
|
|
25691
|
+
});
|
|
25692
|
+
return { result, targetId };
|
|
25693
|
+
};
|
|
25694
|
+
var runIosSimulatorReleaseTarget = async (options) => {
|
|
25695
|
+
const simulator = await selectOrStartIosReleaseSimulator(options.xcrun, valueAfter(options.args, "--udid") ?? valueAfter(options.args, "--serial"));
|
|
25696
|
+
const xcodebuild = requireCapturedCommand([options.xcrun, "--find", "xcodebuild"], "Xcode build-tool inspection").stdout.trim();
|
|
25697
|
+
const result = await runAbsoluteIosSimulatorReleaseAcceptance({
|
|
25698
|
+
artifactDirectory: options.artifactRoot,
|
|
25699
|
+
config: options.mobile,
|
|
25700
|
+
release: options.release,
|
|
25701
|
+
udid: simulator.udid,
|
|
25702
|
+
xcodebuild,
|
|
25703
|
+
xcrun: options.xcrun
|
|
25704
|
+
});
|
|
25705
|
+
return { result, targetId: simulator.udid };
|
|
25706
|
+
};
|
|
25707
|
+
var testIosRelease = async (args, mobile, projectRoot) => {
|
|
25708
|
+
const requested = valueAfter(args, "--release");
|
|
25709
|
+
if (!requested || requested.startsWith("--"))
|
|
25710
|
+
throw new TypeError("mobile test ios --release requires a release directory or release.json path.");
|
|
25711
|
+
if (!mobile.platforms.includes("ios"))
|
|
25712
|
+
throw new TypeError("mobile test ios requires ios in mobile.platforms.");
|
|
25713
|
+
if (args.includes("--remote"))
|
|
25714
|
+
throw new TypeError("Installed iOS release acceptance currently runs on a local Mac. Remote Mac acceptance will be added to the paired-host protocol.");
|
|
25715
|
+
const release = await readAbsoluteIosRelease(projectRoot, requested);
|
|
25716
|
+
if (release.metadata.appId !== mobile.appId)
|
|
25717
|
+
throw new TypeError(`iOS release app ID ${release.metadata.appId} does not match mobile.appId ${mobile.appId}.`);
|
|
25718
|
+
if (release.metadata.engine !== mobile.engine)
|
|
25719
|
+
throw new TypeError(`iOS release engine ${release.metadata.engine} does not match configured engine ${mobile.engine}.`);
|
|
25720
|
+
if (mobile.engine === "expo" && mobile.expoNativeRoutes[mobile.entry])
|
|
25721
|
+
throw new TypeError("Embedded-local Expo release acceptance requires mobile.entry to be a web route; native Expo routes may depend on trusted-server page data.");
|
|
25722
|
+
const requestedDevice = valueAfter(args, "--device");
|
|
25723
|
+
if (args.includes("--device") && !requestedDevice)
|
|
25724
|
+
throw new TypeError("mobile test ios --release --device requires a device identifier or name.");
|
|
25725
|
+
if (args.includes("--testflight") && !requestedDevice)
|
|
25726
|
+
throw new TypeError("mobile test ios --release --testflight requires --device.");
|
|
25727
|
+
if (requestedDevice && (valueAfter(args, "--udid") || valueAfter(args, "--serial")))
|
|
25728
|
+
throw new TypeError("mobile test ios --release --device cannot be combined with a simulator selector.");
|
|
25729
|
+
const xcrun = await requireIosXcrun();
|
|
25730
|
+
const reportRoot = nativeReportRoot(args, projectRoot, "ios");
|
|
25731
|
+
const artifactRoot = reportRoot ?? safeArtifactRoot(projectRoot, valueAfter(args, "--artifacts") ?? `.absolutejs/mobile/test-artifacts/${release.metadata.releaseId}`);
|
|
25732
|
+
const startedAt = performance.now();
|
|
25733
|
+
let targetId = requestedDevice ?? "ios-simulator";
|
|
25734
|
+
try {
|
|
25735
|
+
const acceptance = requestedDevice ? await runIosPhysicalReleaseTarget({
|
|
25736
|
+
args,
|
|
25737
|
+
device: requestedDevice,
|
|
25738
|
+
release,
|
|
25739
|
+
xcrun
|
|
25740
|
+
}) : await runIosSimulatorReleaseTarget({
|
|
25741
|
+
args,
|
|
25742
|
+
artifactRoot,
|
|
25743
|
+
mobile,
|
|
25744
|
+
release,
|
|
25745
|
+
xcrun
|
|
25746
|
+
});
|
|
25747
|
+
const { result, targetId: acceptedTargetId } = acceptance;
|
|
25748
|
+
targetId = acceptedTargetId;
|
|
25749
|
+
sendTelemetryEvent("mobile:ios-release-conformance", {
|
|
25750
|
+
artifactExactness: result.artifactExactness,
|
|
25751
|
+
distribution: result.distribution,
|
|
25752
|
+
durationMs: result.durationMs,
|
|
25753
|
+
engine: result.engine,
|
|
25754
|
+
installMs: result.installMs,
|
|
25755
|
+
launchMs: result.launchMs,
|
|
25756
|
+
platform: "ios",
|
|
25757
|
+
relaunchMs: result.relaunchMs,
|
|
25758
|
+
success: true
|
|
25759
|
+
});
|
|
25760
|
+
await writeRequestedIosReport({
|
|
25761
|
+
args,
|
|
25762
|
+
projectRoot,
|
|
25763
|
+
provider: mobile.engine,
|
|
25764
|
+
run: iosReleaseRun(result, mobile.appId, targetId),
|
|
25765
|
+
xcrun
|
|
25766
|
+
});
|
|
25767
|
+
printIosReleaseAcceptance(result, args.includes("--json"));
|
|
25768
|
+
return result;
|
|
25769
|
+
} catch (error) {
|
|
25770
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
25771
|
+
sendTelemetryEvent("mobile:ios-release-conformance", {
|
|
25772
|
+
durationMs,
|
|
25773
|
+
engine: mobile.engine,
|
|
25774
|
+
platform: "ios",
|
|
25775
|
+
success: false
|
|
25776
|
+
});
|
|
25777
|
+
await mkdir15(artifactRoot, { recursive: true });
|
|
25778
|
+
const diagnosticPath = join16(artifactRoot, "ios-release-failure.json");
|
|
25779
|
+
await writeFile16(diagnosticPath, `${JSON.stringify({
|
|
25780
|
+
error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
|
|
25781
|
+
platform: "ios",
|
|
25782
|
+
provider: mobile.engine,
|
|
25783
|
+
releaseId: release.metadata.releaseId,
|
|
25784
|
+
status: "fail"
|
|
25785
|
+
}, null, 2)}
|
|
25786
|
+
`);
|
|
25787
|
+
if (reportRoot)
|
|
25788
|
+
await writeRequestedIosReport({
|
|
25789
|
+
args,
|
|
25790
|
+
projectRoot,
|
|
25791
|
+
provider: mobile.engine,
|
|
25792
|
+
run: {
|
|
25793
|
+
appId: mobile.appId,
|
|
25794
|
+
durationMs,
|
|
25795
|
+
error: "Installed iOS release acceptance failed; inspect the local diagnostic artifact before sharing evidence.",
|
|
25796
|
+
hmrConnected: false,
|
|
25797
|
+
status: "fail",
|
|
25798
|
+
targetId,
|
|
25799
|
+
targetKind: requestedDevice ? "device" : "simulator"
|
|
25800
|
+
},
|
|
25801
|
+
xcrun
|
|
25802
|
+
});
|
|
25803
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}`, { cause: error });
|
|
25804
|
+
}
|
|
25805
|
+
};
|
|
24938
25806
|
var testIos = async (args) => {
|
|
24939
25807
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
25808
|
+
if (args.includes("--release"))
|
|
25809
|
+
return testIosRelease(args, mobile, projectRoot);
|
|
24940
25810
|
requireCapacitorEngine(mobile, "mobile test ios");
|
|
24941
25811
|
const { https, instance, port } = requireIosTestContext(args, projectRoot);
|
|
24942
25812
|
if (!mobile.platforms.includes("ios"))
|
|
@@ -24996,8 +25866,8 @@ var testIos = async (args) => {
|
|
|
24996
25866
|
mobile.appId
|
|
24997
25867
|
], "iOS app launch");
|
|
24998
25868
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
24999
|
-
await
|
|
25000
|
-
const screenshot =
|
|
25869
|
+
await mkdir15(artifactRoot, { recursive: true });
|
|
25870
|
+
const screenshot = join16(artifactRoot, "ios-simulator.png");
|
|
25001
25871
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
25002
25872
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
25003
25873
|
const report = {
|
|
@@ -25183,7 +26053,7 @@ var runMobile = async (args) => {
|
|
|
25183
26053
|
await publishIos(args.slice(2));
|
|
25184
26054
|
return;
|
|
25185
26055
|
}
|
|
25186
|
-
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] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--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 [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path]");
|
|
26056
|
+
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] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--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 [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
|
|
25187
26057
|
};
|
|
25188
26058
|
export {
|
|
25189
26059
|
runMobile
|