@absolutejs/absolute 0.20.0-beta.100 → 0.20.0-beta.102
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 +25 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/build.js +5 -5
- package/dist/build.js.map +4 -4
- package/dist/cli/{compile-9y36k8z1.js → compile-4xbpm6hf.js} +2 -2
- package/dist/cli/{dev-tn9yfehm.js → dev-m4r4zs2k.js} +2 -2
- package/dist/cli/{expoDevController-mxd3h52y.js → expoDevController-zz516pdd.js} +1 -1
- package/dist/cli/{expoProject-ceey4cfg.js → expoProject-780thyfd.js} +1 -1
- package/dist/cli/{index-kr23b0fp.js → index-28nxv57v.js} +2 -0
- package/dist/cli/{index-0bcf7pdw.js → index-6ff78meg.js} +1 -1
- package/dist/cli/{index-qd6mqgsc.js → index-d5fr1xp3.js} +137 -27
- package/dist/cli/{index-fdycyj4v.js → index-rvpd4w9t.js} +1 -1
- package/dist/cli/index.js +5 -5
- package/dist/cli/{mobile-qfckt3vc.js → mobile-6xe6zysv.js} +851 -233
- package/dist/cli/{start-10dfrjvd.js → start-nmyy64tr.js} +3 -3
- package/dist/index.js +5 -5
- package/dist/index.js.map +4 -4
- package/dist/mobile/index.js +1187 -760
- package/dist/mobile/index.js.map +9 -8
- package/dist/mobile/remoteMacAgentEntry.js +321 -216
- package/dist/src/mobile/androidReleaseAcceptance.d.ts +65 -0
- package/dist/src/mobile/androidTestReport.d.ts +1 -0
- package/dist/src/mobile/index.d.ts +1 -0
- package/dist/src/mobile/nativeTestReport.d.ts +15 -2
- package/dist/src/mobile/releaseDoctor.d.ts +5 -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-rvpd4w9t.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-6ff78meg.js";
|
|
16
16
|
import"./index-bzs19w3r.js";
|
|
17
17
|
import"./index-s2tazax7.js";
|
|
18
18
|
import {
|
|
@@ -67,10 +67,10 @@ import {
|
|
|
67
67
|
syncAbsoluteExpoWebAssets,
|
|
68
68
|
unsignedAbsoluteMobileUpdate,
|
|
69
69
|
writeAbsoluteExpoProject
|
|
70
|
-
} from "./index-
|
|
70
|
+
} from "./index-d5fr1xp3.js";
|
|
71
71
|
import {
|
|
72
72
|
buildAbsoluteExpoAndroidWslBundle
|
|
73
|
-
} from "./index-
|
|
73
|
+
} from "./index-28nxv57v.js";
|
|
74
74
|
import {
|
|
75
75
|
normalizeAbsoluteIosDeviceIdentifier
|
|
76
76
|
} from "./index-j66p8hn5.js";
|
|
@@ -88,6 +88,7 @@ import {
|
|
|
88
88
|
writeAbsoluteCapacitorConfig
|
|
89
89
|
} from "./index-28pbrd3q.js";
|
|
90
90
|
import {
|
|
91
|
+
ABSOLUTE_ANDROID_AVD_NAME,
|
|
91
92
|
absoluteManagedAndroidSdkRoot,
|
|
92
93
|
detectAbsoluteMobileHost,
|
|
93
94
|
inspectAbsoluteMobileToolchain
|
|
@@ -4411,9 +4412,9 @@ var require_sha256 = __commonJS(function(exports, module) {
|
|
|
4411
4412
|
var forge = require_forge();
|
|
4412
4413
|
require_md();
|
|
4413
4414
|
require_util();
|
|
4414
|
-
var
|
|
4415
|
-
forge.md.sha256 = forge.md.algorithms.sha256 =
|
|
4416
|
-
|
|
4415
|
+
var sha2563 = module.exports = forge.sha256 = forge.sha256 || {};
|
|
4416
|
+
forge.md.sha256 = forge.md.algorithms.sha256 = sha2563;
|
|
4417
|
+
sha2563.create = function() {
|
|
4417
4418
|
if (!_initialized) {
|
|
4418
4419
|
_init();
|
|
4419
4420
|
}
|
|
@@ -16775,15 +16776,15 @@ var require_main = __commonJS(function(exports) {
|
|
|
16775
16776
|
|
|
16776
16777
|
// src/cli/scripts/mobile.ts
|
|
16777
16778
|
import {
|
|
16778
|
-
access as
|
|
16779
|
-
mkdir as
|
|
16780
|
-
mkdtemp as
|
|
16781
|
-
readFile as
|
|
16782
|
-
rm as
|
|
16783
|
-
writeFile as
|
|
16779
|
+
access as access13,
|
|
16780
|
+
mkdir as mkdir13,
|
|
16781
|
+
mkdtemp as mkdtemp4,
|
|
16782
|
+
readFile as readFile17,
|
|
16783
|
+
rm as rm7,
|
|
16784
|
+
writeFile as writeFile15
|
|
16784
16785
|
} from "fs/promises";
|
|
16785
16786
|
import { createPublicKey as createPublicKey2 } from "crypto";
|
|
16786
|
-
import { join as
|
|
16787
|
+
import { join as join14, relative as relative12, resolve as resolve13 } from "path";
|
|
16787
16788
|
import { createInterface } from "readline/promises";
|
|
16788
16789
|
|
|
16789
16790
|
// src/mobile/nativeDeepLinks.ts
|
|
@@ -19495,7 +19496,7 @@ var writeAbsoluteMobileUpdateRegistry = async (options) => {
|
|
|
19495
19496
|
|
|
19496
19497
|
// src/mobile/releaseDoctor.ts
|
|
19497
19498
|
var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1;
|
|
19498
|
-
var HMR_ASSET_PATTERN = /(?:
|
|
19499
|
+
var HMR_ASSET_PATTERN = /(?:hmr-timing|__absolute_target|absolutejs-error-overlay)/u;
|
|
19499
19500
|
var RELEASE_ASSET_EXTENSIONS = new Set([".html", ".js", ".mjs"]);
|
|
19500
19501
|
var EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
19501
19502
|
var NOT_FOUND3 = -1;
|
|
@@ -19641,12 +19642,48 @@ var expoVersionCheck = async (config) => {
|
|
|
19641
19642
|
return fail("mobile.expo-versions", error instanceof Error ? error.message : "Generated Expo dependency versions could not be validated.", manifestPath, "Run `absolute mobile init --yes`, then rebuild the production Expo project.");
|
|
19642
19643
|
}
|
|
19643
19644
|
};
|
|
19645
|
+
var dependencyLockAt = async (directory) => (await Promise.all(LOCK_FILES.map(async (name) => ({
|
|
19646
|
+
exists: await pathExists2(join7(directory, name)),
|
|
19647
|
+
name,
|
|
19648
|
+
path: join7(directory, name)
|
|
19649
|
+
})))).find(({ exists: exists2 }) => exists2);
|
|
19650
|
+
var declaredWorkspacePatterns = (declared) => {
|
|
19651
|
+
if (Array.isArray(declared))
|
|
19652
|
+
return declared;
|
|
19653
|
+
if (isRecord3(declared) && Array.isArray(declared.packages))
|
|
19654
|
+
return declared.packages;
|
|
19655
|
+
return [];
|
|
19656
|
+
};
|
|
19657
|
+
var ownsWorkspace = async (directory, projectRoot) => {
|
|
19658
|
+
const manifest = await readJsonObject(join7(directory, "package.json")).catch(() => {
|
|
19659
|
+
return;
|
|
19660
|
+
});
|
|
19661
|
+
const workspacePath = relative3(directory, projectRoot).replace(/\\/gu, "/");
|
|
19662
|
+
return declaredWorkspacePatterns(manifest?.workspaces).some((pattern) => typeof pattern === "string" && new Bun.Glob(pattern).match(workspacePath));
|
|
19663
|
+
};
|
|
19664
|
+
var ancestorDirectories = (projectRoot) => {
|
|
19665
|
+
const directories = [];
|
|
19666
|
+
let directory = projectRoot;
|
|
19667
|
+
while (true) {
|
|
19668
|
+
directories.push(directory);
|
|
19669
|
+
const parent = dirname5(directory);
|
|
19670
|
+
if (parent === directory)
|
|
19671
|
+
return directories;
|
|
19672
|
+
directory = parent;
|
|
19673
|
+
}
|
|
19674
|
+
};
|
|
19675
|
+
var findAbsoluteDependencyLock = async (projectRoot) => {
|
|
19676
|
+
const candidates = await Promise.all(ancestorDirectories(projectRoot).map(async (directory, index) => {
|
|
19677
|
+
const present = await dependencyLockAt(directory);
|
|
19678
|
+
if (!present || index > 0 && !await ownsWorkspace(directory, projectRoot))
|
|
19679
|
+
return;
|
|
19680
|
+
return present;
|
|
19681
|
+
}));
|
|
19682
|
+
return candidates.find((candidate) => candidate !== undefined);
|
|
19683
|
+
};
|
|
19644
19684
|
var dependencyLockCheck = async (projectRoot) => {
|
|
19645
|
-
const present =
|
|
19646
|
-
|
|
19647
|
-
name
|
|
19648
|
-
})))).find(({ exists: exists2 }) => exists2);
|
|
19649
|
-
return present ? pass("mobile.dependency-lock", `Dependency graph is locked by ${present.name}.`, join7(projectRoot, present.name)) : fail("mobile.dependency-lock", "No supported dependency lockfile is present.", projectRoot, "Install dependencies with the project package manager and commit its lockfile before release.");
|
|
19685
|
+
const present = await findAbsoluteDependencyLock(projectRoot);
|
|
19686
|
+
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.");
|
|
19650
19687
|
};
|
|
19651
19688
|
var productionOriginCheck = (config, projectRoot) => {
|
|
19652
19689
|
const origin = new URL(config.productionOrigin);
|
|
@@ -19802,21 +19839,23 @@ var expoEmbeddedAssetsCheck = async (config) => {
|
|
|
19802
19839
|
cwd: assetsRoot,
|
|
19803
19840
|
onlyFiles: true
|
|
19804
19841
|
}))).sort();
|
|
19805
|
-
if (sourceFiles.length === 0 ||
|
|
19842
|
+
if (sourceFiles.length === 0 || embeddedFiles.length !== 1)
|
|
19806
19843
|
throw new TypeError("Generated Expo asset count does not match the prepared mobile bundle.");
|
|
19807
|
-
const
|
|
19808
|
-
|
|
19809
|
-
|
|
19810
|
-
|
|
19811
|
-
|
|
19812
|
-
|
|
19813
|
-
|
|
19814
|
-
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19844
|
+
const contents = await Promise.all(sourceFiles.map((path) => readFile8(join7(config.bundleDirectory, path))));
|
|
19845
|
+
let offset = 0;
|
|
19846
|
+
const entries = sourceFiles.map((path, index) => {
|
|
19847
|
+
const entry = {
|
|
19848
|
+
length: contents[index]?.byteLength ?? 0,
|
|
19849
|
+
offset,
|
|
19850
|
+
path: path.replaceAll("\\", "/")
|
|
19851
|
+
};
|
|
19852
|
+
offset += entry.length;
|
|
19853
|
+
return entry;
|
|
19854
|
+
});
|
|
19855
|
+
const archive = await readFile8(join7(assetsRoot, embeddedFiles[0] ?? ""));
|
|
19856
|
+
if (!archive.equals(Buffer.concat(contents)) || entries.some((entry) => !source.includes(JSON.stringify(entry))))
|
|
19818
19857
|
throw new TypeError("Generated Expo asset bytes differ from the prepared mobile bundle.");
|
|
19819
|
-
return pass("expo.bundle-projection", `Expo embeds the complete signed mobile bundle as ${
|
|
19858
|
+
return pass("expo.bundle-projection", `Expo embeds the complete signed mobile bundle as one opaque archive containing ${sourceFiles.length} file(s).`, generated);
|
|
19820
19859
|
} catch (error) {
|
|
19821
19860
|
return fail("expo.bundle-projection", error instanceof Error ? error.message : "Generated Expo assets could not be validated.", generated, "Run `absolute mobile build <platform>` to regenerate and verify the production asset projection.");
|
|
19822
19861
|
}
|
|
@@ -20383,6 +20422,14 @@ var signAab = (artifactPath, capture, jarsigner, signing) => {
|
|
|
20383
20422
|
if (result.exitCode !== 0)
|
|
20384
20423
|
throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
|
|
20385
20424
|
};
|
|
20425
|
+
var windowsJarsignerPath = (path, capture) => {
|
|
20426
|
+
const result = capture(["wslpath", "-w", path]);
|
|
20427
|
+
const translated = result.stdout.trim();
|
|
20428
|
+
if (result.exitCode !== 0 || !translated) {
|
|
20429
|
+
throw new TypeError(`Could not translate an Android signing path for the Windows JDK: ${path}`);
|
|
20430
|
+
}
|
|
20431
|
+
return translated;
|
|
20432
|
+
};
|
|
20386
20433
|
var sha256File = async (path) => createHash2("sha256").update(await readFile9(path)).digest("hex");
|
|
20387
20434
|
var fingerprintExpoAndroidProject = async (nativeDirectory) => {
|
|
20388
20435
|
const root = await realpath(nativeDirectory);
|
|
@@ -20500,13 +20547,19 @@ var buildAbsoluteAndroidRelease = async (options) => {
|
|
|
20500
20547
|
throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
|
|
20501
20548
|
}
|
|
20502
20549
|
const capture = options.capture ?? defaultCapture;
|
|
20503
|
-
const jarsigner = options.jarsigner === undefined ? Bun.which("jarsigner") : options.jarsigner;
|
|
20504
|
-
|
|
20505
|
-
|
|
20550
|
+
const jarsigner = options.jarsigner === undefined ? Bun.which("jarsigner") ?? (host === "wsl" || host === "windows" ? Bun.which("jarsigner.exe") : null) : options.jarsigner;
|
|
20551
|
+
const usesWindowsJarsigner = host === "wsl" && jarsigner?.toLowerCase().endsWith(".exe");
|
|
20552
|
+
const signerArtifactPath = usesWindowsJarsigner ? windowsJarsignerPath(artifactPath, capture) : artifactPath;
|
|
20553
|
+
const signing = usesWindowsJarsigner && options.signing ? {
|
|
20554
|
+
...options.signing,
|
|
20555
|
+
keystorePath: windowsJarsignerPath(options.signing.keystorePath, capture)
|
|
20556
|
+
} : options.signing;
|
|
20557
|
+
let signed = verifyAabSignature(signerArtifactPath, capture, jarsigner);
|
|
20558
|
+
if (signed === false && signing) {
|
|
20506
20559
|
if (!jarsigner)
|
|
20507
20560
|
throw new TypeError("Could not sign the Android App Bundle because jarsigner is unavailable.");
|
|
20508
|
-
signAab(
|
|
20509
|
-
signed = verifyAabSignature(
|
|
20561
|
+
signAab(signerArtifactPath, capture, jarsigner, signing);
|
|
20562
|
+
signed = verifyAabSignature(signerArtifactPath, capture, jarsigner);
|
|
20510
20563
|
if (!signed)
|
|
20511
20564
|
throw new TypeError("Android App Bundle signature verification failed after CI signing.");
|
|
20512
20565
|
}
|
|
@@ -20538,6 +20591,343 @@ var buildAbsoluteAndroidRelease = async (options) => {
|
|
|
20538
20591
|
return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
|
|
20539
20592
|
};
|
|
20540
20593
|
|
|
20594
|
+
// src/mobile/androidReleaseAcceptance.ts
|
|
20595
|
+
import { createHash as createHash3 } from "crypto";
|
|
20596
|
+
import {
|
|
20597
|
+
access as access6,
|
|
20598
|
+
mkdir as mkdir7,
|
|
20599
|
+
mkdtemp as mkdtemp2,
|
|
20600
|
+
readFile as readFile10,
|
|
20601
|
+
rename as rename8,
|
|
20602
|
+
rm as rm5,
|
|
20603
|
+
stat as stat3,
|
|
20604
|
+
writeFile as writeFile9
|
|
20605
|
+
} from "fs/promises";
|
|
20606
|
+
import { homedir } from "os";
|
|
20607
|
+
import {
|
|
20608
|
+
basename,
|
|
20609
|
+
dirname as dirname7,
|
|
20610
|
+
isAbsolute as isAbsolute3,
|
|
20611
|
+
join as join9,
|
|
20612
|
+
relative as relative5,
|
|
20613
|
+
resolve as resolve6,
|
|
20614
|
+
sep as sep3
|
|
20615
|
+
} from "path";
|
|
20616
|
+
|
|
20617
|
+
// src/mobile/androidUpgradeConformance.ts
|
|
20618
|
+
var captureCommand2 = async (command) => {
|
|
20619
|
+
const process2 = Bun.spawn(command, { stderr: "pipe", stdout: "pipe" });
|
|
20620
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
20621
|
+
process2.exited,
|
|
20622
|
+
new Response(process2.stderr).text(),
|
|
20623
|
+
new Response(process2.stdout).text()
|
|
20624
|
+
]);
|
|
20625
|
+
return { exitCode, stderr, stdout };
|
|
20626
|
+
};
|
|
20627
|
+
var field = (output, name) => new RegExp(`^\\s*${name}=([^\\r\\n]+)$`, "mu").exec(output)?.[1]?.trim();
|
|
20628
|
+
var inspectAbsoluteAndroidInstalledApp = async (adb, serial, appId, run = captureCommand2) => {
|
|
20629
|
+
const result = await run([
|
|
20630
|
+
adb,
|
|
20631
|
+
"-s",
|
|
20632
|
+
serial,
|
|
20633
|
+
"shell",
|
|
20634
|
+
"dumpsys",
|
|
20635
|
+
"package",
|
|
20636
|
+
appId
|
|
20637
|
+
]);
|
|
20638
|
+
if (result.exitCode !== 0)
|
|
20639
|
+
throw new Error(`Could not inspect installed Android app ${appId}: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
20640
|
+
const installed = parseAbsoluteAndroidInstalledApp(appId, result.stdout);
|
|
20641
|
+
if (!installed)
|
|
20642
|
+
throw new Error(`Android app ${appId} is not installed on ${serial}.`);
|
|
20643
|
+
return installed;
|
|
20644
|
+
};
|
|
20645
|
+
var parseAbsoluteAndroidInstalledApp = (appId, output) => {
|
|
20646
|
+
const packageName = /^\s*Package \[([^\]]+)\]/mu.exec(output)?.[1];
|
|
20647
|
+
if (packageName !== appId && !output.includes(`package:${appId}`))
|
|
20648
|
+
return null;
|
|
20649
|
+
const versionCodeText = /^\s*versionCode=(\d+)/mu.exec(output)?.[1];
|
|
20650
|
+
return {
|
|
20651
|
+
appId,
|
|
20652
|
+
...field(output, "dataDir") ? { dataDirectory: field(output, "dataDir") } : {},
|
|
20653
|
+
...field(output, "firstInstallTime") ? { firstInstallTime: field(output, "firstInstallTime") } : {},
|
|
20654
|
+
...field(output, "lastUpdateTime") ? { lastUpdateTime: field(output, "lastUpdateTime") } : {},
|
|
20655
|
+
...field(output, "userId") || field(output, "appId") ? { uid: field(output, "userId") ?? field(output, "appId") } : {},
|
|
20656
|
+
...versionCodeText ? { versionCode: Number(versionCodeText) } : {},
|
|
20657
|
+
...field(output, "versionName") ? { versionName: field(output, "versionName") } : {}
|
|
20658
|
+
};
|
|
20659
|
+
};
|
|
20660
|
+
|
|
20661
|
+
// src/mobile/androidReleaseAcceptance.ts
|
|
20662
|
+
var ABSOLUTE_BUNDLETOOL_VERSION = "1.18.3";
|
|
20663
|
+
var ABSOLUTE_BUNDLETOOL_SHA256 = "a099cfa1543f55593bc2ed16a70a7c67fe54b1747bb7301f37fdfd6d91028e29";
|
|
20664
|
+
var ABSOLUTE_BUNDLETOOL_URL = `https://github.com/google/bundletool/releases/download/${ABSOLUTE_BUNDLETOOL_VERSION}/bundletool-all-${ABSOLUTE_BUNDLETOOL_VERSION}.jar`;
|
|
20665
|
+
var EMBEDDED_READY_MARKER = "Expo embedded web content ready";
|
|
20666
|
+
var RELEASE_READY_POLL_MS = 250;
|
|
20667
|
+
var RELEASE_READY_TIMEOUT_MS = 30000;
|
|
20668
|
+
var RELEASE_STABILITY_MS = 2000;
|
|
20669
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20670
|
+
var exists2 = async (path) => {
|
|
20671
|
+
try {
|
|
20672
|
+
await access6(path);
|
|
20673
|
+
return true;
|
|
20674
|
+
} catch {
|
|
20675
|
+
return false;
|
|
20676
|
+
}
|
|
20677
|
+
};
|
|
20678
|
+
var sha256 = async (path) => createHash3("sha256").update(await readFile10(path)).digest("hex");
|
|
20679
|
+
var inside = (root, candidate) => {
|
|
20680
|
+
const path = relative5(resolve6(root), resolve6(candidate));
|
|
20681
|
+
return path === "" || !isAbsolute3(path) && path !== ".." && !path.startsWith(`..${sep3}`);
|
|
20682
|
+
};
|
|
20683
|
+
var requireString2 = (value, name) => {
|
|
20684
|
+
if (typeof value !== "string" || !value)
|
|
20685
|
+
throw new TypeError(`Android release ${name} is invalid.`);
|
|
20686
|
+
return value;
|
|
20687
|
+
};
|
|
20688
|
+
var parseMetadata = (value) => {
|
|
20689
|
+
if (!isRecord5(value))
|
|
20690
|
+
throw new TypeError("Android release metadata must contain an object.");
|
|
20691
|
+
const { bytes } = value;
|
|
20692
|
+
const { versionCode } = value;
|
|
20693
|
+
if (!Number.isSafeInteger(bytes) || Number(bytes) < 1)
|
|
20694
|
+
throw new TypeError("Android release bytes is invalid.");
|
|
20695
|
+
if (versionCode !== undefined && (!Number.isSafeInteger(versionCode) || Number(versionCode) < 1))
|
|
20696
|
+
throw new TypeError("Android release versionCode is invalid.");
|
|
20697
|
+
if (value.format !== 1 || value.platform !== "android" || value.type !== "aab" || value.engine !== "capacitor" && value.engine !== "expo" || typeof value.signed !== "boolean")
|
|
20698
|
+
throw new TypeError("Android release metadata contract is invalid.");
|
|
20699
|
+
const metadata = {
|
|
20700
|
+
appBuild: requireString2(value.appBuild, "appBuild"),
|
|
20701
|
+
appId: requireString2(value.appId, "appId"),
|
|
20702
|
+
artifact: requireString2(value.artifact, "artifact"),
|
|
20703
|
+
bytes: Number(bytes),
|
|
20704
|
+
engine: value.engine,
|
|
20705
|
+
format: 1,
|
|
20706
|
+
platform: "android",
|
|
20707
|
+
releaseId: requireString2(value.releaseId, "releaseId"),
|
|
20708
|
+
runtime: requireString2(value.runtime, "runtime"),
|
|
20709
|
+
sha256: requireString2(value.sha256, "sha256"),
|
|
20710
|
+
signed: value.signed,
|
|
20711
|
+
type: "aab",
|
|
20712
|
+
...versionCode === undefined ? {} : { versionCode: Number(versionCode) }
|
|
20713
|
+
};
|
|
20714
|
+
if (!/^[a-f0-9]{64}$/u.test(metadata.sha256))
|
|
20715
|
+
throw new TypeError("Android release sha256 is invalid.");
|
|
20716
|
+
if (metadata.releaseId !== `amobile_android_${metadata.sha256}`)
|
|
20717
|
+
throw new TypeError("Android release ID does not match its artifact digest.");
|
|
20718
|
+
return metadata;
|
|
20719
|
+
};
|
|
20720
|
+
var absoluteBundletoolPath = (root = process.env.ABSOLUTE_BUNDLETOOL_ROOT ?? join9(homedir(), ".absolutejs", "tools", "bundletool")) => join9(root, ABSOLUTE_BUNDLETOOL_VERSION, "bundletool.jar");
|
|
20721
|
+
var inspectAbsoluteBundletool = async (root) => {
|
|
20722
|
+
const path = absoluteBundletoolPath(root);
|
|
20723
|
+
if (!await exists2(path))
|
|
20724
|
+
return { path, ready: false };
|
|
20725
|
+
if (await sha256(path) !== ABSOLUTE_BUNDLETOOL_SHA256)
|
|
20726
|
+
throw new TypeError(`Managed Bundletool ${ABSOLUTE_BUNDLETOOL_VERSION} failed checksum verification. Remove ${path} and rerun the command.`);
|
|
20727
|
+
return { path, ready: true };
|
|
20728
|
+
};
|
|
20729
|
+
var readAbsoluteAndroidRelease = async (projectRoot, requested) => {
|
|
20730
|
+
const root = resolve6(projectRoot);
|
|
20731
|
+
const selected = resolve6(root, requested);
|
|
20732
|
+
if (!inside(root, selected))
|
|
20733
|
+
throw new TypeError("mobile test android --release must remain inside the project.");
|
|
20734
|
+
const metadataPath = basename(selected) === "release.json" ? selected : join9(selected, "release.json");
|
|
20735
|
+
const releaseRoot = dirname7(metadataPath);
|
|
20736
|
+
const metadata = parseMetadata(JSON.parse(await readFile10(metadataPath, "utf8")));
|
|
20737
|
+
const artifactPath = resolve6(releaseRoot, metadata.artifact);
|
|
20738
|
+
if (!inside(releaseRoot, artifactPath) || basename(artifactPath) !== metadata.artifact)
|
|
20739
|
+
throw new TypeError("Android release artifact path is invalid.");
|
|
20740
|
+
const [artifactStat, artifactSha256] = await Promise.all([
|
|
20741
|
+
stat3(artifactPath),
|
|
20742
|
+
sha256(artifactPath)
|
|
20743
|
+
]);
|
|
20744
|
+
if (artifactStat.size !== metadata.bytes || artifactSha256 !== metadata.sha256)
|
|
20745
|
+
throw new TypeError(`Immutable Android release ${metadata.releaseId} is missing or modified.`);
|
|
20746
|
+
return { artifactPath, metadata, metadataPath, releaseRoot };
|
|
20747
|
+
};
|
|
20748
|
+
var defaultDownload = async (url) => {
|
|
20749
|
+
const response = await fetch(url);
|
|
20750
|
+
if (!response.ok)
|
|
20751
|
+
throw new Error(`Bundletool download failed (${response.status}).`);
|
|
20752
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
20753
|
+
};
|
|
20754
|
+
var ensureAbsoluteBundletool = async (options) => {
|
|
20755
|
+
const inspected = await inspectAbsoluteBundletool(options.root);
|
|
20756
|
+
if (inspected.ready)
|
|
20757
|
+
return inspected.path;
|
|
20758
|
+
if (!options.approved)
|
|
20759
|
+
throw new TypeError(`Bundletool ${ABSOLUTE_BUNDLETOOL_VERSION} is required. Rerun with --yes to download the checksum-pinned official release.`);
|
|
20760
|
+
const bytes = await (options.download ?? defaultDownload)(ABSOLUTE_BUNDLETOOL_URL);
|
|
20761
|
+
const digest = createHash3("sha256").update(bytes).digest("hex");
|
|
20762
|
+
if (digest !== ABSOLUTE_BUNDLETOOL_SHA256)
|
|
20763
|
+
throw new TypeError("Downloaded Bundletool failed checksum verification.");
|
|
20764
|
+
await mkdir7(dirname7(inspected.path), { recursive: true });
|
|
20765
|
+
const staging = await mkdtemp2(join9(dirname7(inspected.path), ".install-"));
|
|
20766
|
+
const candidate = join9(staging, "bundletool.jar");
|
|
20767
|
+
try {
|
|
20768
|
+
await writeFile9(candidate, bytes, { flag: "wx" });
|
|
20769
|
+
await rename8(candidate, inspected.path).catch(async (error) => {
|
|
20770
|
+
if (await exists2(inspected.path))
|
|
20771
|
+
return;
|
|
20772
|
+
throw error;
|
|
20773
|
+
});
|
|
20774
|
+
} finally {
|
|
20775
|
+
await rm5(staging, { force: true, recursive: true });
|
|
20776
|
+
}
|
|
20777
|
+
if (await sha256(inspected.path) !== ABSOLUTE_BUNDLETOOL_SHA256)
|
|
20778
|
+
throw new TypeError("Installed Bundletool failed checksum verification.");
|
|
20779
|
+
return inspected.path;
|
|
20780
|
+
};
|
|
20781
|
+
var defaultRun = async (command) => {
|
|
20782
|
+
const process2 = Bun.spawn(command, {
|
|
20783
|
+
stderr: "pipe",
|
|
20784
|
+
stdin: "ignore",
|
|
20785
|
+
stdout: "pipe"
|
|
20786
|
+
});
|
|
20787
|
+
const [exitCode, stderr, stdout] = await Promise.all([
|
|
20788
|
+
process2.exited,
|
|
20789
|
+
new Response(process2.stderr).text(),
|
|
20790
|
+
new Response(process2.stdout).text()
|
|
20791
|
+
]);
|
|
20792
|
+
return { exitCode, stderr, stdout };
|
|
20793
|
+
};
|
|
20794
|
+
var requireSuccess = async (run, command, label) => {
|
|
20795
|
+
const result = await run(command);
|
|
20796
|
+
if (result.exitCode !== 0)
|
|
20797
|
+
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim()}`);
|
|
20798
|
+
return result;
|
|
20799
|
+
};
|
|
20800
|
+
var adb = (path, serial, ...args) => [
|
|
20801
|
+
path,
|
|
20802
|
+
"-s",
|
|
20803
|
+
serial,
|
|
20804
|
+
...args
|
|
20805
|
+
];
|
|
20806
|
+
var waitForMarker = async (run, adbPath, serial, appId, timeoutMs) => {
|
|
20807
|
+
const startedAt = performance.now();
|
|
20808
|
+
const inspect = async () => {
|
|
20809
|
+
if (performance.now() - startedAt >= timeoutMs)
|
|
20810
|
+
throw new Error("The installed release launched but its embedded web content did not become ready offline.");
|
|
20811
|
+
const [logs, process2] = await Promise.all([
|
|
20812
|
+
run(adb(adbPath, serial, "logcat", "-d", "-s", "AbsoluteJS:I", "*:S")),
|
|
20813
|
+
run(adb(adbPath, serial, "shell", "pidof", appId))
|
|
20814
|
+
]);
|
|
20815
|
+
if (process2.exitCode !== 0 || !process2.stdout.trim())
|
|
20816
|
+
throw new Error("The installed Android release exited before becoming ready.");
|
|
20817
|
+
if (logs.stdout.includes(EMBEDDED_READY_MARKER))
|
|
20818
|
+
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];
|
|
20820
|
+
if (failedPhase)
|
|
20821
|
+
throw new Error(`The installed release could not initialize its embedded ${failedPhase} runtime offline.`);
|
|
20822
|
+
await Bun.sleep(RELEASE_READY_POLL_MS);
|
|
20823
|
+
await inspect();
|
|
20824
|
+
};
|
|
20825
|
+
return inspect();
|
|
20826
|
+
};
|
|
20827
|
+
var launch = async (run, adbPath, serial, appId, launchUrl) => {
|
|
20828
|
+
const resolved = await requireSuccess(run, adb(adbPath, serial, "shell", "cmd", "package", "resolve-activity", "--brief", appId), "Android launch activity discovery");
|
|
20829
|
+
const activity = resolved.stdout.split(/\r?\n/u).map((line) => line.trim()).findLast((line) => line.includes("/"));
|
|
20830
|
+
if (!activity)
|
|
20831
|
+
throw new Error(`Android could not resolve a launch activity for ${appId}.`);
|
|
20832
|
+
const startedAt = performance.now();
|
|
20833
|
+
await requireSuccess(run, adb(adbPath, serial, "shell", "am", "start", "-W", "-n", activity, ...launchUrl ? ["-a", "android.intent.action.VIEW", "-d", launchUrl] : []), "Android release launch");
|
|
20834
|
+
return Math.round(performance.now() - startedAt);
|
|
20835
|
+
};
|
|
20836
|
+
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
|
+
if (!options.serial.startsWith("emulator-"))
|
|
20840
|
+
throw new TypeError("Installed release acceptance changes network state and therefore requires an Android emulator.");
|
|
20841
|
+
const run = options.run ?? defaultRun;
|
|
20842
|
+
const startedAt = performance.now();
|
|
20843
|
+
await mkdir7(options.artifactDirectory, { recursive: true });
|
|
20844
|
+
const apksPath = join9(options.artifactDirectory, "release.apks");
|
|
20845
|
+
const windowsJava = (options.host ?? detectAbsoluteMobileHost()) === "wsl" && options.java.toLowerCase().endsWith(".exe");
|
|
20846
|
+
const toolPath = async (path) => {
|
|
20847
|
+
if (!windowsJava)
|
|
20848
|
+
return path;
|
|
20849
|
+
const converted = await requireSuccess(run, ["wslpath", "-w", path], "Bundletool Windows path translation");
|
|
20850
|
+
const translated = converted.stdout.trim();
|
|
20851
|
+
if (!translated)
|
|
20852
|
+
throw new Error(`Bundletool could not translate ${basename(path)} for the Windows JDK.`);
|
|
20853
|
+
return translated;
|
|
20854
|
+
};
|
|
20855
|
+
const [bundletoolPath, artifactPath, outputPath, adbPath] = await Promise.all([
|
|
20856
|
+
toolPath(options.bundletool),
|
|
20857
|
+
toolPath(options.release.artifactPath),
|
|
20858
|
+
toolPath(apksPath),
|
|
20859
|
+
toolPath(options.adb)
|
|
20860
|
+
]);
|
|
20861
|
+
await requireSuccess(run, [
|
|
20862
|
+
options.java,
|
|
20863
|
+
"-jar",
|
|
20864
|
+
bundletoolPath,
|
|
20865
|
+
"build-apks",
|
|
20866
|
+
`--bundle=${artifactPath}`,
|
|
20867
|
+
`--output=${outputPath}`,
|
|
20868
|
+
"--mode=universal",
|
|
20869
|
+
"--overwrite"
|
|
20870
|
+
], "Bundletool APK generation");
|
|
20871
|
+
const installStartedAt = performance.now();
|
|
20872
|
+
await requireSuccess(run, [
|
|
20873
|
+
options.java,
|
|
20874
|
+
"-jar",
|
|
20875
|
+
bundletoolPath,
|
|
20876
|
+
"install-apks",
|
|
20877
|
+
`--apks=${outputPath}`,
|
|
20878
|
+
`--adb=${adbPath}`,
|
|
20879
|
+
`--device-id=${options.serial}`
|
|
20880
|
+
], "Bundletool release installation");
|
|
20881
|
+
const installMs = Math.round(performance.now() - installStartedAt);
|
|
20882
|
+
const installed = await inspectAbsoluteAndroidInstalledApp(options.adb, options.serial, options.release.metadata.appId, run);
|
|
20883
|
+
if (options.release.metadata.versionCode !== undefined && installed.versionCode !== options.release.metadata.versionCode)
|
|
20884
|
+
throw new Error(`Installed Android versionCode ${installed.versionCode ?? "unknown"} does not match release ${options.release.metadata.versionCode}.`);
|
|
20885
|
+
await requireSuccess(run, adb(options.adb, options.serial, "logcat", "-c"), "Android release log isolation");
|
|
20886
|
+
const [wifi, mobile] = await Promise.all([
|
|
20887
|
+
requireSuccess(run, adb(options.adb, options.serial, "shell", "settings", "get", "global", "wifi_on"), "Android Wi-Fi state inspection"),
|
|
20888
|
+
requireSuccess(run, adb(options.adb, options.serial, "shell", "settings", "get", "global", "mobile_data"), "Android mobile-data state inspection")
|
|
20889
|
+
]);
|
|
20890
|
+
let launchMs;
|
|
20891
|
+
let relaunchMs;
|
|
20892
|
+
try {
|
|
20893
|
+
await Promise.all([
|
|
20894
|
+
requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "wifi", "disable"), "Android Wi-Fi disable"),
|
|
20895
|
+
requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "data", "disable"), "Android mobile-data disable")
|
|
20896
|
+
]);
|
|
20897
|
+
await requireSuccess(run, adb(options.adb, options.serial, "shell", "am", "force-stop", options.release.metadata.appId), "Android release stop");
|
|
20898
|
+
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);
|
|
20900
|
+
await Bun.sleep(options.stabilityMs ?? RELEASE_STABILITY_MS);
|
|
20901
|
+
await requireSuccess(run, adb(options.adb, options.serial, "shell", "am", "force-stop", options.release.metadata.appId), "Android release relaunch stop");
|
|
20902
|
+
await requireSuccess(run, adb(options.adb, options.serial, "logcat", "-c"), "Android release relaunch log isolation");
|
|
20903
|
+
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);
|
|
20905
|
+
} finally {
|
|
20906
|
+
await Promise.all([
|
|
20907
|
+
requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "wifi", wifi.stdout.trim() === "1" ? "enable" : "disable"), "Android Wi-Fi restore"),
|
|
20908
|
+
requireSuccess(run, adb(options.adb, options.serial, "shell", "svc", "data", mobile.stdout.trim() === "1" ? "enable" : "disable"), "Android mobile-data restore")
|
|
20909
|
+
]);
|
|
20910
|
+
}
|
|
20911
|
+
return {
|
|
20912
|
+
apksBytes: await stat3(apksPath).then(({ size }) => size),
|
|
20913
|
+
artifactBytes: options.release.metadata.bytes,
|
|
20914
|
+
artifactSha256: options.release.metadata.sha256,
|
|
20915
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
20916
|
+
embeddedOffline: true,
|
|
20917
|
+
engine: options.release.metadata.engine,
|
|
20918
|
+
installed: {
|
|
20919
|
+
...installed.versionCode === undefined ? {} : { versionCode: installed.versionCode },
|
|
20920
|
+
...installed.versionName === undefined ? {} : { versionName: installed.versionName }
|
|
20921
|
+
},
|
|
20922
|
+
installMs,
|
|
20923
|
+
launchMs,
|
|
20924
|
+
relaunchMs,
|
|
20925
|
+
releaseId: options.release.metadata.releaseId,
|
|
20926
|
+
signed: options.release.metadata.signed,
|
|
20927
|
+
status: "pass"
|
|
20928
|
+
};
|
|
20929
|
+
};
|
|
20930
|
+
|
|
20541
20931
|
// src/mobile/iosDeviceAcceptance.ts
|
|
20542
20932
|
var absoluteIosDeviceAcceptanceCommands = (options) => {
|
|
20543
20933
|
const xcrun = options.xcrun ?? "/usr/bin/xcrun";
|
|
@@ -20556,20 +20946,20 @@ var absoluteIosDeviceAcceptanceCommands = (options) => {
|
|
|
20556
20946
|
]
|
|
20557
20947
|
};
|
|
20558
20948
|
};
|
|
20559
|
-
var
|
|
20949
|
+
var requireSuccess2 = (result, message) => {
|
|
20560
20950
|
if (result.exitCode !== 0)
|
|
20561
20951
|
throw new Error(message);
|
|
20562
20952
|
return result;
|
|
20563
20953
|
};
|
|
20564
20954
|
var testAbsoluteIosPhysicalDevice = async (options) => {
|
|
20565
20955
|
const commands = absoluteIosDeviceAcceptanceCommands(options);
|
|
20566
|
-
|
|
20567
|
-
const apps =
|
|
20956
|
+
requireSuccess2(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.");
|
|
20957
|
+
const apps = requireSuccess2(await options.capture(commands.apps), "AbsoluteJS could not inspect installed apps on the physical iOS device.");
|
|
20568
20958
|
if (!apps.stdout.includes(options.appId))
|
|
20569
20959
|
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.");
|
|
20570
20960
|
const now = options.now ?? performance.now.bind(performance);
|
|
20571
20961
|
const startedAt = now();
|
|
20572
|
-
|
|
20962
|
+
requireSuccess2(await options.capture(commands.launch), "AbsoluteJS could not relaunch the app on the physical iOS device.");
|
|
20573
20963
|
await options.waitForHmr();
|
|
20574
20964
|
return {
|
|
20575
20965
|
hmrConnected: true,
|
|
@@ -20579,7 +20969,7 @@ var testAbsoluteIosPhysicalDevice = async (options) => {
|
|
|
20579
20969
|
};
|
|
20580
20970
|
|
|
20581
20971
|
// src/mobile/iosConformance.ts
|
|
20582
|
-
import { readFile as
|
|
20972
|
+
import { readFile as readFile11, stat as stat4 } from "fs/promises";
|
|
20583
20973
|
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");
|
|
20584
20974
|
var parseAbsoluteIosHmrLog = (line) => {
|
|
20585
20975
|
const match = HMR_LINE.exec(line);
|
|
@@ -20611,13 +21001,13 @@ var waitForAbsoluteIosHmrLog = async (options) => {
|
|
|
20611
21001
|
const sleep = options.sleep ?? Bun.sleep;
|
|
20612
21002
|
const timeoutMs = options.timeoutMs ?? 30000;
|
|
20613
21003
|
const deadline = Date.now() + timeoutMs;
|
|
20614
|
-
let offset = options.startOffset ?? await
|
|
21004
|
+
let offset = options.startOffset ?? await stat4(options.logPath).then(({ size }) => size).catch(() => 0);
|
|
20615
21005
|
let buffered = "";
|
|
20616
21006
|
const poll = async () => {
|
|
20617
21007
|
if (Date.now() > deadline)
|
|
20618
21008
|
throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
|
|
20619
21009
|
options.signal?.throwIfAborted();
|
|
20620
|
-
const contents = await
|
|
21010
|
+
const contents = await readFile11(options.logPath).catch(() => Buffer.alloc(0));
|
|
20621
21011
|
if (contents.byteLength < offset) {
|
|
20622
21012
|
offset = 0;
|
|
20623
21013
|
buffered = "";
|
|
@@ -20638,8 +21028,8 @@ var waitForAbsoluteIosHmrLog = async (options) => {
|
|
|
20638
21028
|
};
|
|
20639
21029
|
|
|
20640
21030
|
// src/mobile/nativeTestReport.ts
|
|
20641
|
-
import { mkdir as
|
|
20642
|
-
import { join as
|
|
21031
|
+
import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile10 } from "fs/promises";
|
|
21032
|
+
import { join as join10 } from "path";
|
|
20643
21033
|
var secretPattern = /(authorization|access[_ -]?token|refresh[_ -]?token|socket[_ -]?ticket|password|cookie)(\s*[=:]\s*)([^\s,;]+)/giu;
|
|
20644
21034
|
var bearerPattern = /bearer\s+[^\s,;]+/giu;
|
|
20645
21035
|
var coordinatePattern = /\b(latitude|longitude|lat|lng)(\s*[=:]\s*)-?\d+(?:\.\d+)?/giu;
|
|
@@ -20663,6 +21053,14 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
|
|
|
20663
21053
|
artifactDetails = "Physical-device screen capture was intentionally skipped; use Xcode Device Hub for manual visual evidence.";
|
|
20664
21054
|
artifactResult = "SKIPPED";
|
|
20665
21055
|
}
|
|
21056
|
+
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
|
+
if (run.release)
|
|
21058
|
+
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.`;
|
|
21059
|
+
let hmrDetails = "Correlated edit timing was not requested. Rerun with --wait-for-hmr.";
|
|
21060
|
+
if (run.release)
|
|
21061
|
+
hmrDetails = "HMR is intentionally not part of installed production-release acceptance.";
|
|
21062
|
+
if (run.hmr)
|
|
21063
|
+
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)`}.`;
|
|
20666
21064
|
const checks = [
|
|
20667
21065
|
{
|
|
20668
21066
|
details: `Captured host, toolchain, Bun, and AbsoluteJS metadata for ${target}.`,
|
|
@@ -20670,13 +21068,13 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
|
|
|
20670
21068
|
result: "PASS"
|
|
20671
21069
|
},
|
|
20672
21070
|
{
|
|
20673
|
-
details:
|
|
21071
|
+
details: developmentDetails,
|
|
20674
21072
|
...evidence ? { evidence } : {},
|
|
20675
21073
|
id: "AUTO-DEV-01",
|
|
20676
21074
|
result: run.status === "pass" ? "PASS" : "FAIL"
|
|
20677
21075
|
},
|
|
20678
21076
|
{
|
|
20679
|
-
details:
|
|
21077
|
+
details: hmrDetails,
|
|
20680
21078
|
id: "AUTO-HMR-01",
|
|
20681
21079
|
result: hmrResult
|
|
20682
21080
|
},
|
|
@@ -20687,6 +21085,17 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
|
|
|
20687
21085
|
result: artifactResult
|
|
20688
21086
|
}
|
|
20689
21087
|
];
|
|
21088
|
+
if (run.release) {
|
|
21089
|
+
checks.push({
|
|
21090
|
+
details: `Validated immutable release ${run.release.releaseId} (${run.release.artifactBytes} bytes, SHA-256 ${run.release.artifactSha256}); Bundletool produced a ${run.release.apksBytes}-byte APK set and installed it in ${run.release.installMs}ms.`,
|
|
21091
|
+
id: "AUTO-RELEASE-01",
|
|
21092
|
+
result: run.status === "pass" ? "PASS" : "FAIL"
|
|
21093
|
+
}, {
|
|
21094
|
+
details: run.release.embeddedOffline ? "The generated release rendered its embedded web content and survived a process relaunch with Wi-Fi and mobile data disabled." : "The generated release did not prove embedded offline rendering.",
|
|
21095
|
+
id: "AUTO-RELEASE-OFFLINE-01",
|
|
21096
|
+
result: run.release.embeddedOffline ? "PASS" : "FAIL"
|
|
21097
|
+
});
|
|
21098
|
+
}
|
|
20690
21099
|
if (run.upgrade) {
|
|
20691
21100
|
const { upgrade } = run;
|
|
20692
21101
|
checks.push({
|
|
@@ -20749,7 +21158,7 @@ var createAbsoluteNativeTestReport = (options) => ({
|
|
|
20749
21158
|
run: options.run
|
|
20750
21159
|
});
|
|
20751
21160
|
var readPackageVersionForNativeReport = async (packageJsonPath) => {
|
|
20752
|
-
const manifest = JSON.parse(await
|
|
21161
|
+
const manifest = JSON.parse(await readFile12(packageJsonPath, "utf8"));
|
|
20753
21162
|
if (typeof manifest !== "object" || manifest === null)
|
|
20754
21163
|
return "unknown";
|
|
20755
21164
|
const version = Reflect.get(manifest, "version");
|
|
@@ -20787,13 +21196,13 @@ ${table(report.manualChecks)}
|
|
|
20787
21196
|
`;
|
|
20788
21197
|
};
|
|
20789
21198
|
var writeAbsoluteNativeTestReport = async (directory, report) => {
|
|
20790
|
-
await
|
|
20791
|
-
const jsonPath =
|
|
20792
|
-
const markdownPath =
|
|
21199
|
+
await mkdir8(directory, { recursive: true });
|
|
21200
|
+
const jsonPath = join10(directory, "report.json");
|
|
21201
|
+
const markdownPath = join10(directory, "report.md");
|
|
20793
21202
|
await Promise.all([
|
|
20794
|
-
|
|
21203
|
+
writeFile10(jsonPath, `${JSON.stringify(report, null, 2)}
|
|
20795
21204
|
`),
|
|
20796
|
-
|
|
21205
|
+
writeFile10(markdownPath, renderAbsoluteNativeTestReport(report))
|
|
20797
21206
|
]);
|
|
20798
21207
|
return { directory, jsonPath, markdownPath };
|
|
20799
21208
|
};
|
|
@@ -21021,6 +21430,8 @@ var MANUAL_CHECKS2 = [
|
|
|
21021
21430
|
"Review this directory for sensitive content and complete every row."
|
|
21022
21431
|
]
|
|
21023
21432
|
];
|
|
21433
|
+
var portableAdbVersion = (version) => version.split(/\r?\n/u).filter((line) => !/^\s*(?:Installed as|Running on)\b/iu.test(line)).join(`
|
|
21434
|
+
`).trim();
|
|
21024
21435
|
var createAbsoluteAndroidTestReport = (options) => {
|
|
21025
21436
|
const { serial, ...run } = options.run;
|
|
21026
21437
|
return createAbsoluteNativeTestReport({
|
|
@@ -21028,10 +21439,10 @@ var createAbsoluteAndroidTestReport = (options) => {
|
|
|
21028
21439
|
manualChecks: MANUAL_CHECKS2,
|
|
21029
21440
|
metadata: {
|
|
21030
21441
|
absolutejsVersion: options.absolutejsVersion,
|
|
21031
|
-
adbVersion: options.adbVersion,
|
|
21442
|
+
adbVersion: portableAdbVersion(options.adbVersion),
|
|
21032
21443
|
bunVersion: options.bunVersion,
|
|
21033
21444
|
host: options.host,
|
|
21034
|
-
provider: "capacitor"
|
|
21445
|
+
provider: options.provider ?? "capacitor"
|
|
21035
21446
|
},
|
|
21036
21447
|
run: {
|
|
21037
21448
|
...run,
|
|
@@ -21043,8 +21454,8 @@ var createAbsoluteAndroidTestReport = (options) => {
|
|
|
21043
21454
|
};
|
|
21044
21455
|
|
|
21045
21456
|
// src/mobile/releasePublisher.ts
|
|
21046
|
-
import { access as
|
|
21047
|
-
import { isAbsolute as
|
|
21457
|
+
import { access as access7 } from "fs/promises";
|
|
21458
|
+
import { isAbsolute as isAbsolute4, relative as relative6, resolve as resolve7, sep as sep4 } from "path";
|
|
21048
21459
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
21049
21460
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
21050
21461
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -21067,24 +21478,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
|
|
|
21067
21478
|
}
|
|
21068
21479
|
return versionCode;
|
|
21069
21480
|
};
|
|
21070
|
-
var
|
|
21071
|
-
var isPublisher = (value) =>
|
|
21481
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21482
|
+
var isPublisher = (value) => isRecord6(value) && typeof value.publish === "function";
|
|
21072
21483
|
var publisherModulePath = (projectRoot, requested) => {
|
|
21073
|
-
const root =
|
|
21074
|
-
const path =
|
|
21075
|
-
const projectRelative =
|
|
21076
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
21484
|
+
const root = resolve7(projectRoot);
|
|
21485
|
+
const path = resolve7(root, requested);
|
|
21486
|
+
const projectRelative = relative6(root, path);
|
|
21487
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep4}`) || isAbsolute4(projectRelative)) {
|
|
21077
21488
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
21078
21489
|
}
|
|
21079
21490
|
return path;
|
|
21080
21491
|
};
|
|
21081
21492
|
var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
|
|
21082
21493
|
const modulePath = publisherModulePath(projectRoot, requestedModulePath);
|
|
21083
|
-
await
|
|
21494
|
+
await access7(modulePath).catch(() => {
|
|
21084
21495
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
21085
21496
|
});
|
|
21086
21497
|
const loaded = await import(pathToFileURL2(modulePath).href);
|
|
21087
|
-
const publisher =
|
|
21498
|
+
const publisher = isRecord6(loaded) ? loaded.default ?? loaded.registry : undefined;
|
|
21088
21499
|
if (!isPublisher(publisher)) {
|
|
21089
21500
|
throw new TypeError("Native release registry module must default-export a registry with publish(options).");
|
|
21090
21501
|
}
|
|
@@ -21142,8 +21553,8 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
21142
21553
|
};
|
|
21143
21554
|
|
|
21144
21555
|
// src/mobile/mobileInspect.ts
|
|
21145
|
-
import { access as
|
|
21146
|
-
import { join as
|
|
21556
|
+
import { access as access8, readFile as readFile13 } from "fs/promises";
|
|
21557
|
+
import { join as join11, relative as relative7, resolve as resolve8 } from "path";
|
|
21147
21558
|
var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1;
|
|
21148
21559
|
var MOBILE_PACKAGE_NAMES = new Set([
|
|
21149
21560
|
"@absolutejs/absolute",
|
|
@@ -21158,19 +21569,19 @@ var MOBILE_PACKAGE_NAMES = new Set([
|
|
|
21158
21569
|
]);
|
|
21159
21570
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21160
21571
|
var portablePath2 = (projectRoot, path) => {
|
|
21161
|
-
const value =
|
|
21572
|
+
const value = relative7(resolve8(projectRoot), resolve8(path)).replaceAll("\\", "/");
|
|
21162
21573
|
return value || ".";
|
|
21163
21574
|
};
|
|
21164
21575
|
var pathExists4 = async (path) => {
|
|
21165
21576
|
try {
|
|
21166
|
-
await
|
|
21577
|
+
await access8(path);
|
|
21167
21578
|
return true;
|
|
21168
21579
|
} catch {
|
|
21169
21580
|
return false;
|
|
21170
21581
|
}
|
|
21171
21582
|
};
|
|
21172
21583
|
var readObject2 = async (path) => {
|
|
21173
|
-
const value = JSON.parse(await
|
|
21584
|
+
const value = JSON.parse(await readFile13(path, "utf8"));
|
|
21174
21585
|
if (!isObject2(value))
|
|
21175
21586
|
throw new TypeError("JSON root must be an object.");
|
|
21176
21587
|
return value;
|
|
@@ -21182,13 +21593,13 @@ var addPackageDeclarations = (declarations, value) => {
|
|
|
21182
21593
|
declarations.set(name, declared);
|
|
21183
21594
|
};
|
|
21184
21595
|
var packageInspections = async (projectRoot, additionalNames) => {
|
|
21185
|
-
const project = await readObject2(
|
|
21596
|
+
const project = await readObject2(join11(projectRoot, "package.json"));
|
|
21186
21597
|
const declarations = new Map;
|
|
21187
|
-
for (const
|
|
21188
|
-
addPackageDeclarations(declarations, project[
|
|
21598
|
+
for (const field2 of ["dependencies", "devDependencies"])
|
|
21599
|
+
addPackageDeclarations(declarations, project[field2]);
|
|
21189
21600
|
const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
|
|
21190
21601
|
return Promise.all(names.map(async (name) => {
|
|
21191
|
-
const installedManifest = await readObject2(
|
|
21602
|
+
const installedManifest = await readObject2(join11(projectRoot, "node_modules", name, "package.json")).catch(() => {
|
|
21192
21603
|
return;
|
|
21193
21604
|
});
|
|
21194
21605
|
const installed = installedManifest?.version;
|
|
@@ -21247,7 +21658,7 @@ var inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) =>
|
|
|
21247
21658
|
},
|
|
21248
21659
|
format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
|
|
21249
21660
|
nativeProjects: await Promise.all(config.platforms.map(async (platform) => {
|
|
21250
|
-
const path =
|
|
21661
|
+
const path = join11(config.nativeProjectDirectory, platform);
|
|
21251
21662
|
return {
|
|
21252
21663
|
initialized: await pathExists4(path),
|
|
21253
21664
|
path: portablePath2(projectRoot, path),
|
|
@@ -21304,8 +21715,8 @@ var renderAbsoluteMobileProjectInspection = (report) => {
|
|
|
21304
21715
|
|
|
21305
21716
|
// src/mobile/ciWorkflow.ts
|
|
21306
21717
|
import { existsSync } from "fs";
|
|
21307
|
-
import { access as
|
|
21308
|
-
import { dirname as
|
|
21718
|
+
import { access as access9, mkdir as mkdir9, readFile as readFile14, writeFile as writeFile11 } from "fs/promises";
|
|
21719
|
+
import { dirname as dirname8, extname as extname2, relative as relative8, resolve as resolve9, sep as sep5 } from "path";
|
|
21309
21720
|
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
|
|
21310
21721
|
var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
|
|
21311
21722
|
var CI_ENV_INDENTATION = 6;
|
|
@@ -21324,34 +21735,34 @@ var RESERVED_SECRET_NAMES = new Set([
|
|
|
21324
21735
|
"APP_STORE_CONNECT_KEY_ID",
|
|
21325
21736
|
"APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
|
|
21326
21737
|
]);
|
|
21327
|
-
var
|
|
21738
|
+
var exists3 = async (path) => {
|
|
21328
21739
|
try {
|
|
21329
|
-
await
|
|
21740
|
+
await access9(path);
|
|
21330
21741
|
return true;
|
|
21331
21742
|
} catch {
|
|
21332
21743
|
return false;
|
|
21333
21744
|
}
|
|
21334
21745
|
};
|
|
21335
21746
|
var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
21336
|
-
var projectPath2 = (projectRoot, value,
|
|
21337
|
-
const root =
|
|
21338
|
-
const path =
|
|
21339
|
-
const portable =
|
|
21340
|
-
if (portable === ".." || portable.startsWith(`..${
|
|
21341
|
-
throw new TypeError(`${
|
|
21747
|
+
var projectPath2 = (projectRoot, value, field2, options = {}) => {
|
|
21748
|
+
const root = resolve9(projectRoot);
|
|
21749
|
+
const path = resolve9(root, value);
|
|
21750
|
+
const portable = relative8(root, path).replaceAll("\\", "/");
|
|
21751
|
+
if (portable === ".." || portable.startsWith(`..${sep5}`) || portable.startsWith("../") || portable === "") {
|
|
21752
|
+
throw new TypeError(`${field2} must remain inside the project root.`);
|
|
21342
21753
|
}
|
|
21343
21754
|
if (/\r|\n/u.test(portable) || portable.startsWith("-"))
|
|
21344
|
-
throw new TypeError(`${
|
|
21755
|
+
throw new TypeError(`${field2} contains an unsafe path.`);
|
|
21345
21756
|
if (!options.allowMissing && !existsSync(path))
|
|
21346
|
-
throw new TypeError(`${
|
|
21757
|
+
throw new TypeError(`${field2} does not exist inside the project.`);
|
|
21347
21758
|
return portable;
|
|
21348
21759
|
};
|
|
21349
21760
|
var workflowOutputPath = (projectRoot, value) => {
|
|
21350
|
-
const root =
|
|
21351
|
-
const workflows =
|
|
21352
|
-
const path =
|
|
21353
|
-
const portable =
|
|
21354
|
-
if (portable === ".." || portable.startsWith(`..${
|
|
21761
|
+
const root = resolve9(projectRoot);
|
|
21762
|
+
const workflows = resolve9(root, ".github/workflows");
|
|
21763
|
+
const path = resolve9(root, value ?? ".github/workflows/absolute-mobile.yml");
|
|
21764
|
+
const portable = relative8(workflows, path);
|
|
21765
|
+
if (portable === ".." || portable.startsWith(`..${sep5}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
|
|
21355
21766
|
throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
|
|
21356
21767
|
}
|
|
21357
21768
|
return path;
|
|
@@ -21780,12 +22191,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
|
|
|
21780
22191
|
var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
21781
22192
|
const path = workflowOutputPath(options.projectRoot, options.outputPath);
|
|
21782
22193
|
const generated = createAbsoluteMobileGithubWorkflow(options);
|
|
21783
|
-
const previous = await
|
|
22194
|
+
const previous = await exists3(path) ? await readFile14(path, "utf8") : undefined;
|
|
21784
22195
|
if (previous !== undefined && previous !== generated.workflow && !options.force)
|
|
21785
|
-
throw new TypeError(`${
|
|
22196
|
+
throw new TypeError(`${relative8(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
|
|
21786
22197
|
if (previous !== generated.workflow) {
|
|
21787
|
-
await
|
|
21788
|
-
await
|
|
22198
|
+
await mkdir9(dirname8(path), { recursive: true });
|
|
22199
|
+
await writeFile11(path, generated.workflow);
|
|
21789
22200
|
}
|
|
21790
22201
|
return {
|
|
21791
22202
|
changed: previous !== generated.workflow,
|
|
@@ -21798,46 +22209,46 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
|
21798
22209
|
};
|
|
21799
22210
|
|
|
21800
22211
|
// src/mobile/updateSigning.ts
|
|
21801
|
-
import { createHash as
|
|
22212
|
+
import { createHash as createHash4, sign, verify } from "crypto";
|
|
21802
22213
|
import {
|
|
21803
22214
|
cp,
|
|
21804
|
-
mkdir as
|
|
21805
|
-
mkdtemp as
|
|
22215
|
+
mkdir as mkdir10,
|
|
22216
|
+
mkdtemp as mkdtemp3,
|
|
21806
22217
|
readdir as readdir4,
|
|
21807
|
-
readFile as
|
|
21808
|
-
rename as
|
|
21809
|
-
rm as
|
|
21810
|
-
stat as
|
|
21811
|
-
writeFile as
|
|
22218
|
+
readFile as readFile15,
|
|
22219
|
+
rename as rename9,
|
|
22220
|
+
rm as rm6,
|
|
22221
|
+
stat as stat5,
|
|
22222
|
+
writeFile as writeFile12
|
|
21812
22223
|
} from "fs/promises";
|
|
21813
|
-
import { dirname as
|
|
22224
|
+
import { dirname as dirname9, join as join12, relative as relative9, resolve as resolve10 } from "path";
|
|
21814
22225
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
21815
22226
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
21816
|
-
var
|
|
22227
|
+
var sha2562 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
21817
22228
|
var listFiles = async (root, directory = root) => {
|
|
21818
22229
|
const entries = await readdir4(directory, { withFileTypes: true });
|
|
21819
22230
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
21820
|
-
const path =
|
|
22231
|
+
const path = join12(directory, entry.name);
|
|
21821
22232
|
if (entry.isDirectory())
|
|
21822
22233
|
return listFiles(root, path);
|
|
21823
22234
|
if (!entry.isFile())
|
|
21824
22235
|
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
21825
|
-
return [
|
|
22236
|
+
return [relative9(root, path).replaceAll("\\", "/")];
|
|
21826
22237
|
}));
|
|
21827
22238
|
return paths.flat().sort((left, right) => left.localeCompare(right));
|
|
21828
22239
|
};
|
|
21829
22240
|
var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
21830
|
-
const source =
|
|
22241
|
+
const source = join12(root, path);
|
|
21831
22242
|
const [metadata, contents] = await Promise.all([
|
|
21832
|
-
|
|
21833
|
-
|
|
22243
|
+
stat5(source),
|
|
22244
|
+
readFile15(source)
|
|
21834
22245
|
]);
|
|
21835
|
-
return { bytes: metadata.size, path, sha256:
|
|
22246
|
+
return { bytes: metadata.size, path, sha256: sha2562(contents) };
|
|
21836
22247
|
}));
|
|
21837
|
-
var releaseIdFor = (value) => `amu_${
|
|
22248
|
+
var releaseIdFor = (value) => `amu_${sha2562(canonicalizeAbsoluteMobileUpdate(value))}`;
|
|
21838
22249
|
var buildAbsoluteMobileUpdate = async (options) => {
|
|
21839
|
-
const bundleDirectory =
|
|
21840
|
-
const outputRoot =
|
|
22250
|
+
const bundleDirectory = resolve10(options.bundleDirectory);
|
|
22251
|
+
const outputRoot = resolve10(options.outputDirectory);
|
|
21841
22252
|
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
21842
22253
|
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
21843
22254
|
const paths = await listFiles(bundleDirectory);
|
|
@@ -21865,30 +22276,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
21865
22276
|
value: signature.toString("base64")
|
|
21866
22277
|
}
|
|
21867
22278
|
});
|
|
21868
|
-
await
|
|
21869
|
-
const outputDirectory =
|
|
21870
|
-
const staging = await
|
|
22279
|
+
await mkdir10(outputRoot, { recursive: true });
|
|
22280
|
+
const outputDirectory = join12(outputRoot, manifest.releaseId);
|
|
22281
|
+
const staging = await mkdtemp3(join12(outputRoot, ".stage-"));
|
|
21871
22282
|
try {
|
|
21872
|
-
await cp(bundleDirectory,
|
|
22283
|
+
await cp(bundleDirectory, join12(staging, UPDATE_FILES_DIRECTORY), {
|
|
21873
22284
|
force: true,
|
|
21874
22285
|
recursive: true
|
|
21875
22286
|
});
|
|
21876
|
-
await
|
|
22287
|
+
await writeFile12(join12(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
21877
22288
|
`);
|
|
21878
|
-
await
|
|
22289
|
+
await rename9(staging, outputDirectory);
|
|
21879
22290
|
} catch (error) {
|
|
21880
|
-
await
|
|
22291
|
+
await rm6(staging, { force: true, recursive: true });
|
|
21881
22292
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
21882
22293
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
21883
22294
|
throw error;
|
|
21884
22295
|
}
|
|
21885
22296
|
return {
|
|
21886
22297
|
manifest,
|
|
21887
|
-
manifestPath:
|
|
22298
|
+
manifestPath: join12(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
21888
22299
|
outputDirectory
|
|
21889
22300
|
};
|
|
21890
22301
|
};
|
|
21891
|
-
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await
|
|
22302
|
+
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile15(join12(resolve10(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
21892
22303
|
var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
21893
22304
|
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
21894
22305
|
const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
@@ -21898,17 +22309,17 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
|
21898
22309
|
};
|
|
21899
22310
|
|
|
21900
22311
|
// src/mobile/expoUpdate.ts
|
|
21901
|
-
import { access as
|
|
21902
|
-
import { dirname as
|
|
22312
|
+
import { access as access10, mkdir as mkdir11, readFile as readFile16, writeFile as writeFile13 } from "fs/promises";
|
|
22313
|
+
import { dirname as dirname10, extname as extname3, join as join13 } from "path";
|
|
21903
22314
|
var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
|
|
21904
22315
|
var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
|
|
21905
22316
|
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21906
|
-
var safePath = (value,
|
|
22317
|
+
var safePath = (value, field2) => {
|
|
21907
22318
|
if (typeof value !== "string" || value.length === 0)
|
|
21908
|
-
throw new TypeError(`Expo update ${
|
|
22319
|
+
throw new TypeError(`Expo update ${field2} is invalid.`);
|
|
21909
22320
|
const path = value.replaceAll("\\", "/");
|
|
21910
22321
|
if (path.startsWith("/") || path.split("/").some((segment) => !segment || segment === "." || segment === ".."))
|
|
21911
|
-
throw new TypeError(`Expo update ${
|
|
22322
|
+
throw new TypeError(`Expo update ${field2} must be a relative path.`);
|
|
21912
22323
|
return path;
|
|
21913
22324
|
};
|
|
21914
22325
|
var extension = (value, path) => {
|
|
@@ -21965,7 +22376,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
|
|
|
21965
22376
|
};
|
|
21966
22377
|
};
|
|
21967
22378
|
var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
21968
|
-
const metadata = JSON.parse(await
|
|
22379
|
+
const metadata = JSON.parse(await readFile16(join13(options.exportDirectory, "metadata.json"), "utf8"));
|
|
21969
22380
|
if (!object2(metadata) || !object2(metadata.fileMetadata))
|
|
21970
22381
|
throw new TypeError("Expo export metadata.json is invalid.");
|
|
21971
22382
|
const descriptor = parseAbsoluteExpoUpdateDescriptor({
|
|
@@ -21976,33 +22387,33 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
|
21976
22387
|
runtimeVersion: options.runtimeVersion
|
|
21977
22388
|
});
|
|
21978
22389
|
const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
|
|
21979
|
-
await Promise.all(referenced.map(({ path }) =>
|
|
22390
|
+
await Promise.all(referenced.map(({ path }) => access10(join13(options.exportDirectory, path)).catch(() => {
|
|
21980
22391
|
throw new TypeError(`Expo export metadata references missing asset ${path}.`);
|
|
21981
22392
|
})));
|
|
21982
|
-
const destination =
|
|
21983
|
-
await
|
|
21984
|
-
await
|
|
22393
|
+
const destination = join13(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
|
|
22394
|
+
await mkdir11(dirname10(destination), { recursive: true });
|
|
22395
|
+
await writeFile13(destination, `${JSON.stringify(descriptor, null, "\t")}
|
|
21985
22396
|
`);
|
|
21986
22397
|
return { descriptor, path: destination };
|
|
21987
22398
|
};
|
|
21988
22399
|
|
|
21989
22400
|
// src/mobile/expoCodeSigning.ts
|
|
21990
22401
|
var import_code_signing_certificates = __toESM(require_main(), 1);
|
|
21991
|
-
import { access as
|
|
21992
|
-
import { dirname as
|
|
21993
|
-
var
|
|
21994
|
-
var
|
|
21995
|
-
const location =
|
|
21996
|
-
return location === "" || location !== ".." && !location.startsWith(`..${
|
|
22402
|
+
import { access as access11, mkdir as mkdir12, writeFile as writeFile14 } from "fs/promises";
|
|
22403
|
+
import { dirname as dirname11, isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep6 } from "path";
|
|
22404
|
+
var exists4 = async (path) => access11(path).then(() => true).catch(() => false);
|
|
22405
|
+
var inside2 = (root, path) => {
|
|
22406
|
+
const location = relative10(root, path);
|
|
22407
|
+
return location === "" || location !== ".." && !location.startsWith(`..${sep6}`) && !isAbsolute5(location);
|
|
21997
22408
|
};
|
|
21998
22409
|
var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
21999
|
-
const root =
|
|
22000
|
-
const certificatePath =
|
|
22001
|
-
const privateKeyPath =
|
|
22002
|
-
const publicKeyPath =
|
|
22003
|
-
if (!
|
|
22410
|
+
const root = resolve11(options.projectRoot);
|
|
22411
|
+
const certificatePath = resolve11(root, options.certificatePath);
|
|
22412
|
+
const privateKeyPath = resolve11(root, options.privateKeyPath);
|
|
22413
|
+
const publicKeyPath = resolve11(root, options.publicKeyPath ?? resolve11(dirname11(privateKeyPath), "public-key.pem"));
|
|
22414
|
+
if (!inside2(root, certificatePath))
|
|
22004
22415
|
throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
|
|
22005
|
-
if (
|
|
22416
|
+
if (inside2(root, privateKeyPath) || inside2(root, publicKeyPath))
|
|
22006
22417
|
throw new TypeError("Expo code-signing private/public key output must remain outside the project and source control.");
|
|
22007
22418
|
const commonName = options.commonName.trim();
|
|
22008
22419
|
if (!commonName || commonName.length > 64)
|
|
@@ -22011,7 +22422,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
22011
22422
|
if (!Number.isSafeInteger(validityYears) || validityYears < 1 || validityYears > 20)
|
|
22012
22423
|
throw new TypeError("Expo code-signing validity must be an integer from 1 through 20 years.");
|
|
22013
22424
|
const collisions = (await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map(async (path) => ({
|
|
22014
|
-
exists: await
|
|
22425
|
+
exists: await exists4(path),
|
|
22015
22426
|
path
|
|
22016
22427
|
})))).filter((entry) => entry.exists);
|
|
22017
22428
|
if (collisions.length > 0)
|
|
@@ -22029,11 +22440,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
22029
22440
|
import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
|
|
22030
22441
|
const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
|
|
22031
22442
|
const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
|
|
22032
|
-
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) =>
|
|
22443
|
+
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir12(dirname11(path), { recursive: true })));
|
|
22033
22444
|
await Promise.all([
|
|
22034
|
-
|
|
22035
|
-
|
|
22036
|
-
|
|
22445
|
+
writeFile14(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
|
|
22446
|
+
writeFile14(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
|
|
22447
|
+
writeFile14(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
|
|
22037
22448
|
]);
|
|
22038
22449
|
return {
|
|
22039
22450
|
certificatePath,
|
|
@@ -22044,8 +22455,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
22044
22455
|
};
|
|
22045
22456
|
|
|
22046
22457
|
// src/mobile/updatePublisher.ts
|
|
22047
|
-
import { access as
|
|
22048
|
-
import { isAbsolute as
|
|
22458
|
+
import { access as access12 } from "fs/promises";
|
|
22459
|
+
import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve12, sep as sep7 } from "path";
|
|
22049
22460
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
22050
22461
|
var lifecycleMethod = (publisher, name) => {
|
|
22051
22462
|
const method = publisher[name];
|
|
@@ -22154,16 +22565,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
|
|
|
22154
22565
|
var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22155
22566
|
var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
|
|
22156
22567
|
var projectPath3 = (projectRoot, requested, label) => {
|
|
22157
|
-
const root =
|
|
22158
|
-
const path =
|
|
22159
|
-
const projectRelative =
|
|
22160
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
22568
|
+
const root = resolve12(projectRoot);
|
|
22569
|
+
const path = resolve12(root, requested);
|
|
22570
|
+
const projectRelative = relative11(root, path);
|
|
22571
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep7}`) || isAbsolute6(projectRelative))
|
|
22161
22572
|
throw new TypeError(`${label} must remain inside the project.`);
|
|
22162
22573
|
return path;
|
|
22163
22574
|
};
|
|
22164
22575
|
var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
22165
22576
|
const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
|
|
22166
|
-
await
|
|
22577
|
+
await access12(modulePath).catch(() => {
|
|
22167
22578
|
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
22168
22579
|
});
|
|
22169
22580
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
@@ -22216,10 +22627,12 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
|
|
|
22216
22627
|
|
|
22217
22628
|
// src/cli/scripts/mobile.ts
|
|
22218
22629
|
var NOT_FOUND4 = -1;
|
|
22219
|
-
var
|
|
22630
|
+
var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
|
|
22631
|
+
var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
|
|
22632
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22220
22633
|
var expoPublicConfig = (config) => {
|
|
22221
22634
|
const result = { ...config };
|
|
22222
|
-
if (!
|
|
22635
|
+
if (!isRecord7(result.updates))
|
|
22223
22636
|
return result;
|
|
22224
22637
|
const {
|
|
22225
22638
|
codeSigningCertificate: _codeSigningCertificate,
|
|
@@ -22259,13 +22672,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
|
|
|
22259
22672
|
];
|
|
22260
22673
|
var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
|
|
22261
22674
|
var directProjectPackages = async (projectRoot) => {
|
|
22262
|
-
const manifest = JSON.parse(await
|
|
22263
|
-
if (!
|
|
22675
|
+
const manifest = JSON.parse(await readFile17(join14(projectRoot, "package.json"), "utf8"));
|
|
22676
|
+
if (!isRecord7(manifest))
|
|
22264
22677
|
throw new TypeError("Application package.json must contain an object.");
|
|
22265
22678
|
const names = new Set;
|
|
22266
|
-
for (const
|
|
22267
|
-
const dependencies = Reflect.get(manifest,
|
|
22268
|
-
if (
|
|
22679
|
+
for (const field2 of ["dependencies", "devDependencies"]) {
|
|
22680
|
+
const dependencies = Reflect.get(manifest, field2);
|
|
22681
|
+
if (isRecord7(dependencies))
|
|
22269
22682
|
for (const name of Object.keys(dependencies))
|
|
22270
22683
|
names.add(name);
|
|
22271
22684
|
}
|
|
@@ -22273,8 +22686,8 @@ var directProjectPackages = async (projectRoot) => {
|
|
|
22273
22686
|
};
|
|
22274
22687
|
var resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
22275
22688
|
try {
|
|
22276
|
-
const manifest = JSON.parse(await
|
|
22277
|
-
return
|
|
22689
|
+
const manifest = JSON.parse(await readFile17(join14(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
22690
|
+
return isRecord7(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
22278
22691
|
} catch {
|
|
22279
22692
|
return;
|
|
22280
22693
|
}
|
|
@@ -22324,9 +22737,9 @@ var requireMobileConfig = (value) => {
|
|
|
22324
22737
|
return value;
|
|
22325
22738
|
};
|
|
22326
22739
|
var capacitorExecutable = async (projectRoot) => {
|
|
22327
|
-
const executable =
|
|
22740
|
+
const executable = join14(projectRoot, "node_modules", ".bin", "cap");
|
|
22328
22741
|
try {
|
|
22329
|
-
await
|
|
22742
|
+
await access13(executable);
|
|
22330
22743
|
return executable;
|
|
22331
22744
|
} catch {
|
|
22332
22745
|
throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
|
|
@@ -22347,9 +22760,9 @@ var runCapacitor = async (projectRoot, args) => {
|
|
|
22347
22760
|
};
|
|
22348
22761
|
var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
|
|
22349
22762
|
var expoExecutable = async (project) => {
|
|
22350
|
-
const executable =
|
|
22763
|
+
const executable = join14(project, "node_modules", ".bin", "expo");
|
|
22351
22764
|
try {
|
|
22352
|
-
await
|
|
22765
|
+
await access13(executable);
|
|
22353
22766
|
return executable;
|
|
22354
22767
|
} catch {
|
|
22355
22768
|
throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
|
|
@@ -22381,14 +22794,14 @@ var runExpo = async (project, args, options = {}) => {
|
|
|
22381
22794
|
};
|
|
22382
22795
|
var ensureExpoPackages = async (project, args) => {
|
|
22383
22796
|
try {
|
|
22384
|
-
const manifest = JSON.parse(await
|
|
22797
|
+
const manifest = JSON.parse(await readFile17(join14(project, "package.json"), "utf8"));
|
|
22385
22798
|
const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
|
|
22386
22799
|
if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
|
|
22387
22800
|
throw new TypeError("Generated Expo dependencies are invalid.");
|
|
22388
22801
|
await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
|
|
22389
22802
|
if (typeof expected !== "string")
|
|
22390
22803
|
throw new TypeError("Generated Expo dependency version is invalid.");
|
|
22391
|
-
const installed = JSON.parse(await
|
|
22804
|
+
const installed = JSON.parse(await readFile17(join14(project, "node_modules", name, "package.json"), "utf8"));
|
|
22392
22805
|
const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
|
|
22393
22806
|
if (typeof actual !== "string")
|
|
22394
22807
|
throw new TypeError("Installed Expo dependency version is invalid.");
|
|
@@ -22515,7 +22928,7 @@ var unpairRemoteMac = async (args) => {
|
|
|
22515
22928
|
var initialize = async (args) => {
|
|
22516
22929
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
22517
22930
|
if (mobile.engine === "expo") {
|
|
22518
|
-
console.warn("Experimental: Expo Android
|
|
22931
|
+
console.warn("Experimental: Expo Android and iOS release automation is available; real macOS and physical-device acceptance remains pending.");
|
|
22519
22932
|
const generated2 = await writeAbsoluteExpoProject(mobile, {
|
|
22520
22933
|
force: args.includes("--force"),
|
|
22521
22934
|
projectRoot
|
|
@@ -22584,7 +22997,7 @@ var sync = async (args) => {
|
|
|
22584
22997
|
};
|
|
22585
22998
|
var associations = async (args) => {
|
|
22586
22999
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
22587
|
-
const outputDirectory =
|
|
23000
|
+
const outputDirectory = resolve13(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
22588
23001
|
if (args.includes("--verify")) {
|
|
22589
23002
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
22590
23003
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -22636,7 +23049,7 @@ var generateGithubCi = async (args) => {
|
|
|
22636
23049
|
const publicResult = {
|
|
22637
23050
|
changed: result.changed,
|
|
22638
23051
|
format: result.format,
|
|
22639
|
-
path:
|
|
23052
|
+
path: relative12(projectRoot, result.path).replaceAll("\\", "/"),
|
|
22640
23053
|
platforms: result.platforms,
|
|
22641
23054
|
publishing: result.publishing,
|
|
22642
23055
|
requiredSecrets: result.requiredSecrets
|
|
@@ -22747,9 +23160,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
22747
23160
|
temporaryDirectory: undefined
|
|
22748
23161
|
};
|
|
22749
23162
|
await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
|
|
22750
|
-
const temporaryParent =
|
|
22751
|
-
await
|
|
22752
|
-
const temporaryDirectory = await
|
|
23163
|
+
const temporaryParent = join14(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
|
|
23164
|
+
await mkdir13(temporaryParent, { recursive: true });
|
|
23165
|
+
const temporaryDirectory = await mkdtemp4(join14(temporaryParent, ".stage-"));
|
|
22753
23166
|
try {
|
|
22754
23167
|
await runExpo(mobile.nativeProjectDirectory, [
|
|
22755
23168
|
"export",
|
|
@@ -22761,9 +23174,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
22761
23174
|
temporaryDirectory,
|
|
22762
23175
|
"--clear"
|
|
22763
23176
|
], { production: true });
|
|
22764
|
-
const generatedApp = JSON.parse(await
|
|
22765
|
-
const expoConfig =
|
|
22766
|
-
if (!
|
|
23177
|
+
const generatedApp = JSON.parse(await readFile17(join14(mobile.nativeProjectDirectory, "app.json"), "utf8"));
|
|
23178
|
+
const expoConfig = isRecord7(generatedApp) ? generatedApp.expo : undefined;
|
|
23179
|
+
if (!isRecord7(expoConfig))
|
|
22767
23180
|
throw new TypeError("Generated Expo app configuration is invalid.");
|
|
22768
23181
|
const publicExpoConfig = expoPublicConfig(expoConfig);
|
|
22769
23182
|
const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
|
|
@@ -22779,7 +23192,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
22779
23192
|
temporaryDirectory
|
|
22780
23193
|
};
|
|
22781
23194
|
} catch (error) {
|
|
22782
|
-
await
|
|
23195
|
+
await rm7(temporaryDirectory, { force: true, recursive: true });
|
|
22783
23196
|
throw error;
|
|
22784
23197
|
}
|
|
22785
23198
|
};
|
|
@@ -22804,11 +23217,11 @@ var buildMobileUpdate = async (args) => {
|
|
|
22804
23217
|
let expoExportDirectory;
|
|
22805
23218
|
try {
|
|
22806
23219
|
await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
|
|
22807
|
-
const embedded = JSON.parse(await
|
|
22808
|
-
const runtimeFingerprint =
|
|
23220
|
+
const embedded = JSON.parse(await readFile17(join14(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
|
|
23221
|
+
const runtimeFingerprint = isRecord7(embedded) ? embedded.nativeRuntime : undefined;
|
|
22809
23222
|
if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
|
|
22810
23223
|
throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
|
|
22811
|
-
const privateKey = await
|
|
23224
|
+
const privateKey = await readFile17(resolve13(projectRoot, signingKeyPath));
|
|
22812
23225
|
const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
|
|
22813
23226
|
const derivedPublicKey = createPublicKey2(privateKey).export({
|
|
22814
23227
|
format: "der",
|
|
@@ -22829,7 +23242,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
22829
23242
|
channel: mobile.updates.channel,
|
|
22830
23243
|
classification,
|
|
22831
23244
|
keyId,
|
|
22832
|
-
outputDirectory: valueAfter(args, "--outdir") ??
|
|
23245
|
+
outputDirectory: valueAfter(args, "--outdir") ?? join14(projectRoot, ".absolutejs", "mobile", "updates"),
|
|
22833
23246
|
privateKey,
|
|
22834
23247
|
runtimeFingerprint
|
|
22835
23248
|
});
|
|
@@ -22841,7 +23254,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
22841
23254
|
return result;
|
|
22842
23255
|
} finally {
|
|
22843
23256
|
if (expoExportDirectory)
|
|
22844
|
-
await
|
|
23257
|
+
await rm7(expoExportDirectory, { force: true, recursive: true });
|
|
22845
23258
|
sendTelemetryEvent("mobile:update-build", {
|
|
22846
23259
|
classification: classification ?? "invalid",
|
|
22847
23260
|
durationMs: Math.round(performance.now() - startedAt),
|
|
@@ -22871,7 +23284,7 @@ var generateExpoUpdateSigning = async (args) => {
|
|
|
22871
23284
|
...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
|
|
22872
23285
|
...validityYears === undefined ? {} : { validityYears }
|
|
22873
23286
|
});
|
|
22874
|
-
const certificatePath =
|
|
23287
|
+
const certificatePath = relative12(projectRoot, result.certificatePath).replaceAll("\\", "/");
|
|
22875
23288
|
sendTelemetryEvent("mobile:update-code-signing-generated", {
|
|
22876
23289
|
engine: "expo",
|
|
22877
23290
|
validityYears: validityYears ?? 10
|
|
@@ -22933,7 +23346,7 @@ var provisionMobileUpdate = async (args) => {
|
|
|
22933
23346
|
publicKeys: mobile.updates.publicKeys,
|
|
22934
23347
|
storage: requestedStorage
|
|
22935
23348
|
});
|
|
22936
|
-
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${
|
|
23349
|
+
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative12(projectRoot, path).replaceAll("\\", "/")}.`);
|
|
22937
23350
|
if (requestedStorage === "local")
|
|
22938
23351
|
console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
|
|
22939
23352
|
else
|
|
@@ -23375,8 +23788,10 @@ var buildAndroid = async (args, prepareVersionCode) => {
|
|
|
23375
23788
|
phaseSuccess = true;
|
|
23376
23789
|
return result;
|
|
23377
23790
|
} finally {
|
|
23791
|
+
const durationMs = Math.round(performance.now() - phaseStartedAt);
|
|
23792
|
+
console.log(`[mobile:android-release] ${phase} ${phaseSuccess ? "completed" : "failed"} in ${getDurationString(durationMs)}`);
|
|
23378
23793
|
sendTelemetryEvent("mobile:android-release-phase", {
|
|
23379
|
-
durationMs
|
|
23794
|
+
durationMs,
|
|
23380
23795
|
engine: mobile.engine,
|
|
23381
23796
|
host,
|
|
23382
23797
|
phase,
|
|
@@ -23406,7 +23821,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
|
|
|
23406
23821
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
23407
23822
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
23408
23823
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
23409
|
-
console.log(`Metadata: ${
|
|
23824
|
+
console.log(`Metadata: ${join14(release.releaseRoot, "release.json")}`);
|
|
23410
23825
|
return release;
|
|
23411
23826
|
} finally {
|
|
23412
23827
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -23562,7 +23977,7 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
23562
23977
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
23563
23978
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} iOS IPA ${release.metadata.marketingVersion}${release.metadata.buildNumber ? ` (${release.metadata.buildNumber})` : ""} in ${getDurationString(durationMs)}.`);
|
|
23564
23979
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
23565
|
-
console.log(`Metadata: ${
|
|
23980
|
+
console.log(`Metadata: ${join14(release.releaseRoot, "release.json")}`);
|
|
23566
23981
|
return release;
|
|
23567
23982
|
} finally {
|
|
23568
23983
|
stopListeningForCancellation();
|
|
@@ -23674,7 +24089,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
23674
24089
|
checks.push({
|
|
23675
24090
|
id: "sync.storage-schema",
|
|
23676
24091
|
label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
|
|
23677
|
-
path:
|
|
24092
|
+
path: join14(projectRoot, "package.json"),
|
|
23678
24093
|
platform: "host",
|
|
23679
24094
|
status: "pass"
|
|
23680
24095
|
});
|
|
@@ -23682,7 +24097,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
23682
24097
|
checks.push({
|
|
23683
24098
|
id: "sync.storage-schema",
|
|
23684
24099
|
label: "Offline schema metadata is invalid",
|
|
23685
|
-
path:
|
|
24100
|
+
path: join14(projectRoot, "package.json"),
|
|
23686
24101
|
platform: "host",
|
|
23687
24102
|
remediation: error instanceof Error ? error.message : String(error),
|
|
23688
24103
|
status: "fail"
|
|
@@ -23749,7 +24164,7 @@ AbsoluteJS can configure ${installPlatform} emulation:`);
|
|
|
23749
24164
|
Emulator setup verification:`);
|
|
23750
24165
|
printDoctorChecks(result.checks.filter((check) => check.platform === "host" || check.platform === installPlatform));
|
|
23751
24166
|
};
|
|
23752
|
-
var
|
|
24167
|
+
var captureCommand3 = (command) => {
|
|
23753
24168
|
const result = Bun.spawnSync(command, {
|
|
23754
24169
|
stderr: "pipe",
|
|
23755
24170
|
stdin: "ignore",
|
|
@@ -23770,7 +24185,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
|
|
|
23770
24185
|
}
|
|
23771
24186
|
return { https: args.includes("--https"), port };
|
|
23772
24187
|
}
|
|
23773
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
24188
|
+
const instances = listLiveInstances().filter((instance2) => resolve13(instance2.cwd) === resolve13(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
23774
24189
|
if (instances.length !== 1) {
|
|
23775
24190
|
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>.");
|
|
23776
24191
|
}
|
|
@@ -23792,14 +24207,14 @@ var androidTestTimeout = (args) => {
|
|
|
23792
24207
|
};
|
|
23793
24208
|
var requireAndroidAdb = async () => {
|
|
23794
24209
|
const checks = await inspectAbsoluteMobileToolchain();
|
|
23795
|
-
const
|
|
23796
|
-
if (!
|
|
24210
|
+
const adb2 = checks.find((check) => check.id === "android.adb" && check.status === "pass")?.path;
|
|
24211
|
+
if (!adb2) {
|
|
23797
24212
|
throw new TypeError("Android Debug Bridge is unavailable. Run `absolute mobile doctor android --fix`.");
|
|
23798
24213
|
}
|
|
23799
|
-
return
|
|
24214
|
+
return adb2;
|
|
23800
24215
|
};
|
|
23801
|
-
var selectAndroidSerial = (
|
|
23802
|
-
const devices =
|
|
24216
|
+
var selectAndroidSerial = (adb2, explicitSerial) => {
|
|
24217
|
+
const devices = captureCommand3([adb2, "devices"]);
|
|
23803
24218
|
if (devices.exitCode !== 0) {
|
|
23804
24219
|
throw new Error(`Could not list Android targets: ${devices.stderr.trim() || devices.stdout.trim()}`);
|
|
23805
24220
|
}
|
|
@@ -23816,9 +24231,56 @@ var selectAndroidSerial = (adb, explicitSerial) => {
|
|
|
23816
24231
|
}
|
|
23817
24232
|
return selected;
|
|
23818
24233
|
};
|
|
24234
|
+
var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial, json) => {
|
|
24235
|
+
if (explicitSerial)
|
|
24236
|
+
return selectAndroidSerial(adb2, explicitSerial);
|
|
24237
|
+
const current = captureCommand3([adb2, "devices"]);
|
|
24238
|
+
const ready = current.exitCode === 0 ? parseAdbDevices(current.stdout).find((serial) => serial.startsWith("emulator-")) : undefined;
|
|
24239
|
+
if (ready)
|
|
24240
|
+
return ready;
|
|
24241
|
+
if (!emulator)
|
|
24242
|
+
throw new TypeError("Android Emulator is unavailable. Run `absolute mobile doctor android --fix`.");
|
|
24243
|
+
(json ? console.error : console.log)(`Starting managed Android emulator ${ABSOLUTE_ANDROID_AVD_NAME}\u2026`);
|
|
24244
|
+
const launched = Bun.spawn([
|
|
24245
|
+
emulator,
|
|
24246
|
+
"-avd",
|
|
24247
|
+
ABSOLUTE_ANDROID_AVD_NAME,
|
|
24248
|
+
"-netdelay",
|
|
24249
|
+
"none",
|
|
24250
|
+
"-netspeed",
|
|
24251
|
+
"full"
|
|
24252
|
+
], { stderr: "ignore", stdin: "ignore", stdout: "ignore" });
|
|
24253
|
+
launched.unref();
|
|
24254
|
+
const startedAt = performance.now();
|
|
24255
|
+
const bootedSerial = () => {
|
|
24256
|
+
const devices = captureCommand3([adb2, "devices"]);
|
|
24257
|
+
const serial = devices.exitCode === 0 ? parseAdbDevices(devices.stdout).find((candidate) => candidate.startsWith("emulator-")) : undefined;
|
|
24258
|
+
if (!serial)
|
|
24259
|
+
return;
|
|
24260
|
+
const booted = captureCommand3([
|
|
24261
|
+
adb2,
|
|
24262
|
+
"-s",
|
|
24263
|
+
serial,
|
|
24264
|
+
"shell",
|
|
24265
|
+
"getprop",
|
|
24266
|
+
"sys.boot_completed"
|
|
24267
|
+
]);
|
|
24268
|
+
return booted.exitCode === 0 && booted.stdout.trim() === "1" ? serial : undefined;
|
|
24269
|
+
};
|
|
24270
|
+
const waitForBoot = async () => {
|
|
24271
|
+
if (performance.now() - startedAt >= ANDROID_EMULATOR_BOOT_TIMEOUT_MS)
|
|
24272
|
+
throw new Error(`Managed Android emulator ${ABSOLUTE_ANDROID_AVD_NAME} did not finish booting within 180 seconds.`);
|
|
24273
|
+
const serial = bootedSerial();
|
|
24274
|
+
if (serial)
|
|
24275
|
+
return serial;
|
|
24276
|
+
await Bun.sleep(ANDROID_EMULATOR_BOOT_POLL_MS);
|
|
24277
|
+
return waitForBoot();
|
|
24278
|
+
};
|
|
24279
|
+
return waitForBoot();
|
|
24280
|
+
};
|
|
23819
24281
|
var safeArtifactRoot = (projectRoot, value) => {
|
|
23820
|
-
const root =
|
|
23821
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
24282
|
+
const root = resolve13(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
24283
|
+
if (root !== projectRoot && !root.startsWith(`${resolve13(projectRoot)}/`)) {
|
|
23822
24284
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
23823
24285
|
}
|
|
23824
24286
|
return root;
|
|
@@ -23846,12 +24308,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
|
|
|
23846
24308
|
});
|
|
23847
24309
|
};
|
|
23848
24310
|
var writeAndroidFailureArtifacts = async (options) => {
|
|
23849
|
-
await
|
|
23850
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
24311
|
+
await mkdir13(options.artifactRoot, { recursive: true });
|
|
24312
|
+
const screenshot = options.session ? await options.session.screenshot(join14(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
23851
24313
|
return;
|
|
23852
24314
|
}) : undefined;
|
|
23853
|
-
const diagnosticPath =
|
|
23854
|
-
await
|
|
24315
|
+
const diagnosticPath = join14(options.artifactRoot, "android-failure.json");
|
|
24316
|
+
await writeFile15(diagnosticPath, `${JSON.stringify({
|
|
23855
24317
|
diagnostics: options.session?.diagnostics ?? [],
|
|
23856
24318
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
23857
24319
|
platform: "android",
|
|
@@ -23863,16 +24325,171 @@ var writeAndroidFailureArtifacts = async (options) => {
|
|
|
23863
24325
|
`);
|
|
23864
24326
|
return { diagnosticPath, screenshot };
|
|
23865
24327
|
};
|
|
24328
|
+
var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
|
|
24329
|
+
const process2 = Bun.spawn([adb2, "-s", serial, "exec-out", "screencap", "-p"], { stderr: "pipe", stdin: "ignore", stdout: "pipe" });
|
|
24330
|
+
const [exitCode, bytes, stderr] = await Promise.all([
|
|
24331
|
+
process2.exited,
|
|
24332
|
+
new Response(process2.stdout).bytes(),
|
|
24333
|
+
new Response(process2.stderr).text()
|
|
24334
|
+
]);
|
|
24335
|
+
if (exitCode !== 0)
|
|
24336
|
+
throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
|
|
24337
|
+
await writeFile15(destination, bytes);
|
|
24338
|
+
return destination;
|
|
24339
|
+
};
|
|
24340
|
+
var printAndroidReleaseAcceptance = (result, json) => {
|
|
24341
|
+
if (json) {
|
|
24342
|
+
console.log(JSON.stringify(result, null, 2));
|
|
24343
|
+
return;
|
|
24344
|
+
}
|
|
24345
|
+
console.log(`\u2713 Installed immutable ${result.engine} release ${result.releaseId} with Bundletool in ${getDurationString(result.installMs)}.`);
|
|
24346
|
+
console.log(`\u2713 Embedded web content booted offline in ${getDurationString(result.launchMs)} and relaunched in ${getDurationString(result.relaunchMs)}.`);
|
|
24347
|
+
};
|
|
24348
|
+
var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
24349
|
+
const requested = valueAfter(args, "--release");
|
|
24350
|
+
if (!requested || requested.startsWith("--"))
|
|
24351
|
+
throw new TypeError("mobile test android --release requires a release directory or release.json path.");
|
|
24352
|
+
if (!mobile.platforms.includes("android"))
|
|
24353
|
+
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
|
+
const release = await readAbsoluteAndroidRelease(projectRoot, requested);
|
|
24357
|
+
if (release.metadata.appId !== mobile.appId)
|
|
24358
|
+
throw new TypeError(`Android release app ID ${release.metadata.appId} does not match mobile.appId ${mobile.appId}.`);
|
|
24359
|
+
if (release.metadata.engine !== mobile.engine)
|
|
24360
|
+
throw new TypeError(`Android release engine ${release.metadata.engine} does not match configured engine ${mobile.engine}.`);
|
|
24361
|
+
if (mobile.engine === "expo" && mobile.expoNativeRoutes[mobile.entry])
|
|
24362
|
+
throw new TypeError("Offline Expo release acceptance requires mobile.entry to be an embedded web route; the configured entry is native and requires trusted server page data.");
|
|
24363
|
+
const checks = await inspectAbsoluteMobileToolchain();
|
|
24364
|
+
const adb2 = checks.find((check) => check.id === "android.adb" && check.status === "pass")?.path;
|
|
24365
|
+
const java = checks.find((check) => check.id === "android.java" && check.status === "pass")?.path;
|
|
24366
|
+
const emulator = checks.find((check) => check.id === "android.emulator" && check.status === "pass")?.path;
|
|
24367
|
+
if (!adb2)
|
|
24368
|
+
throw new TypeError("Android Debug Bridge is unavailable. Run `absolute mobile doctor android --fix`.");
|
|
24369
|
+
if (!java)
|
|
24370
|
+
throw new TypeError("Java is unavailable. Install the JDK required by the Android toolchain and rerun `absolute mobile doctor android`.");
|
|
24371
|
+
const serial = await selectOrStartAndroidReleaseEmulator(adb2, emulator, valueAfter(args, "--serial"), args.includes("--json"));
|
|
24372
|
+
const inspectedBundletool = await inspectAbsoluteBundletool();
|
|
24373
|
+
const approved = inspectedBundletool.ready || args.includes("--yes") || await confirmInstall(`Bundletool ${ABSOLUTE_BUNDLETOOL_VERSION} is required to install the exact App Bundle. Download and checksum-verify it now?`);
|
|
24374
|
+
const bundletool = await ensureAbsoluteBundletool({
|
|
24375
|
+
approved: approved === true
|
|
24376
|
+
});
|
|
24377
|
+
const reportRoot = nativeReportRoot(args, projectRoot, "android");
|
|
24378
|
+
const artifactRoot = reportRoot ?? safeArtifactRoot(projectRoot, valueAfter(args, "--artifacts") ?? `.absolutejs/mobile/test-artifacts/${release.metadata.releaseId}`);
|
|
24379
|
+
const startedAt = performance.now();
|
|
24380
|
+
try {
|
|
24381
|
+
const result = await runAbsoluteAndroidReleaseAcceptance({
|
|
24382
|
+
adb: adb2,
|
|
24383
|
+
artifactDirectory: artifactRoot,
|
|
24384
|
+
bundletool,
|
|
24385
|
+
host: detectAbsoluteMobileHost(),
|
|
24386
|
+
java,
|
|
24387
|
+
...mobile.deepLinkScheme ? {
|
|
24388
|
+
launchUrl: `${mobile.deepLinkScheme}://${mobile.entry}`
|
|
24389
|
+
} : {},
|
|
24390
|
+
release,
|
|
24391
|
+
serial
|
|
24392
|
+
});
|
|
24393
|
+
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join14(reportRoot, "android-release.png")) : undefined;
|
|
24394
|
+
sendTelemetryEvent("mobile:android-release-conformance", {
|
|
24395
|
+
durationMs: result.durationMs,
|
|
24396
|
+
engine: result.engine,
|
|
24397
|
+
installMs: result.installMs,
|
|
24398
|
+
launchMs: result.launchMs,
|
|
24399
|
+
platform: "android",
|
|
24400
|
+
relaunchMs: result.relaunchMs,
|
|
24401
|
+
success: true
|
|
24402
|
+
});
|
|
24403
|
+
await writeRequestedAndroidReport({
|
|
24404
|
+
adb: adb2,
|
|
24405
|
+
args,
|
|
24406
|
+
projectRoot,
|
|
24407
|
+
provider: mobile.engine,
|
|
24408
|
+
reportRoot,
|
|
24409
|
+
run: {
|
|
24410
|
+
appId: mobile.appId,
|
|
24411
|
+
durationMs: result.durationMs,
|
|
24412
|
+
hmrConnected: false,
|
|
24413
|
+
release: {
|
|
24414
|
+
apksBytes: result.apksBytes,
|
|
24415
|
+
artifactBytes: result.artifactBytes,
|
|
24416
|
+
artifactSha256: result.artifactSha256,
|
|
24417
|
+
embeddedOffline: result.embeddedOffline,
|
|
24418
|
+
engine: result.engine,
|
|
24419
|
+
installMs: result.installMs,
|
|
24420
|
+
launchMs: result.launchMs,
|
|
24421
|
+
relaunchMs: result.relaunchMs,
|
|
24422
|
+
releaseId: result.releaseId,
|
|
24423
|
+
signed: result.signed
|
|
24424
|
+
},
|
|
24425
|
+
...screenshot ? { screenshot } : {},
|
|
24426
|
+
serial,
|
|
24427
|
+
status: "pass"
|
|
24428
|
+
}
|
|
24429
|
+
});
|
|
24430
|
+
printAndroidReleaseAcceptance(result, args.includes("--json"));
|
|
24431
|
+
return result;
|
|
24432
|
+
} catch (error) {
|
|
24433
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
24434
|
+
sendTelemetryEvent("mobile:android-release-conformance", {
|
|
24435
|
+
durationMs,
|
|
24436
|
+
engine: mobile.engine,
|
|
24437
|
+
platform: "android",
|
|
24438
|
+
success: false
|
|
24439
|
+
});
|
|
24440
|
+
await mkdir13(artifactRoot, { recursive: true });
|
|
24441
|
+
const diagnosticPath = join14(artifactRoot, "android-release-failure.json");
|
|
24442
|
+
await writeFile15(diagnosticPath, `${JSON.stringify({
|
|
24443
|
+
error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
|
|
24444
|
+
platform: "android",
|
|
24445
|
+
provider: mobile.engine,
|
|
24446
|
+
releaseId: release.metadata.releaseId,
|
|
24447
|
+
status: "fail"
|
|
24448
|
+
}, null, 2)}
|
|
24449
|
+
`);
|
|
24450
|
+
await writeRequestedAndroidReport({
|
|
24451
|
+
adb: adb2,
|
|
24452
|
+
args,
|
|
24453
|
+
projectRoot,
|
|
24454
|
+
provider: mobile.engine,
|
|
24455
|
+
reportRoot,
|
|
24456
|
+
run: {
|
|
24457
|
+
appId: mobile.appId,
|
|
24458
|
+
durationMs,
|
|
24459
|
+
error: "Installed release acceptance failed; inspect the local diagnostic artifact before sharing evidence.",
|
|
24460
|
+
hmrConnected: false,
|
|
24461
|
+
release: {
|
|
24462
|
+
apksBytes: 0,
|
|
24463
|
+
artifactBytes: release.metadata.bytes,
|
|
24464
|
+
artifactSha256: release.metadata.sha256,
|
|
24465
|
+
embeddedOffline: false,
|
|
24466
|
+
engine: release.metadata.engine,
|
|
24467
|
+
installMs: 0,
|
|
24468
|
+
launchMs: 0,
|
|
24469
|
+
relaunchMs: 0,
|
|
24470
|
+
releaseId: release.metadata.releaseId,
|
|
24471
|
+
signed: release.metadata.signed
|
|
24472
|
+
},
|
|
24473
|
+
serial,
|
|
24474
|
+
status: "fail"
|
|
24475
|
+
}
|
|
24476
|
+
});
|
|
24477
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}`, { cause: error });
|
|
24478
|
+
}
|
|
24479
|
+
};
|
|
23866
24480
|
var testAndroid = async (args) => {
|
|
23867
24481
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
24482
|
+
if (args.includes("--release")) {
|
|
24483
|
+
return testAndroidRelease(args, mobile, projectRoot);
|
|
24484
|
+
}
|
|
23868
24485
|
requireCapacitorEngine(mobile, "mobile test android");
|
|
23869
24486
|
if (!mobile.platforms.includes("android")) {
|
|
23870
24487
|
throw new TypeError("mobile test android requires android in mobile.platforms.");
|
|
23871
24488
|
}
|
|
23872
24489
|
const { https, port } = requireAndroidTestPort(args, projectRoot);
|
|
23873
24490
|
const timeoutMs = androidTestTimeout(args);
|
|
23874
|
-
const
|
|
23875
|
-
const serial = selectAndroidSerial(
|
|
24491
|
+
const adb2 = await requireAndroidAdb();
|
|
24492
|
+
const serial = selectAndroidSerial(adb2, valueAfter(args, "--serial"));
|
|
23876
24493
|
const routes = valuesAfter(args, "--route");
|
|
23877
24494
|
if (routes.length === 0)
|
|
23878
24495
|
routes.push(mobile.entry);
|
|
@@ -23882,7 +24499,7 @@ var testAndroid = async (args) => {
|
|
|
23882
24499
|
let session;
|
|
23883
24500
|
try {
|
|
23884
24501
|
session = await attachAbsoluteAndroidWebView({
|
|
23885
|
-
adb,
|
|
24502
|
+
adb: adb2,
|
|
23886
24503
|
appId: mobile.appId,
|
|
23887
24504
|
serial,
|
|
23888
24505
|
timeoutMs
|
|
@@ -23916,9 +24533,9 @@ var testAndroid = async (args) => {
|
|
|
23916
24533
|
console.log(JSON.stringify(report, null, 2));
|
|
23917
24534
|
else
|
|
23918
24535
|
printAndroidTestReport(report);
|
|
23919
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
24536
|
+
const screenshot = reportRoot ? await session.screenshot(join14(artifactRoot, "android-emulator.png")) : undefined;
|
|
23920
24537
|
await writeRequestedAndroidReport({
|
|
23921
|
-
adb,
|
|
24538
|
+
adb: adb2,
|
|
23922
24539
|
args,
|
|
23923
24540
|
projectRoot,
|
|
23924
24541
|
run: {
|
|
@@ -23959,7 +24576,7 @@ var testAndroid = async (args) => {
|
|
|
23959
24576
|
session
|
|
23960
24577
|
});
|
|
23961
24578
|
await writeRequestedAndroidReport({
|
|
23962
|
-
adb,
|
|
24579
|
+
adb: adb2,
|
|
23963
24580
|
args,
|
|
23964
24581
|
projectRoot,
|
|
23965
24582
|
run: {
|
|
@@ -23985,14 +24602,14 @@ var requireIosTestContext = (args, projectRoot) => {
|
|
|
23985
24602
|
const port = Number(explicit);
|
|
23986
24603
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
23987
24604
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
23988
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
24605
|
+
const instance2 = listLiveInstances().find((candidate) => resolve13(candidate.cwd) === resolve13(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
23989
24606
|
return {
|
|
23990
24607
|
https: instance2?.https ?? args.includes("--https"),
|
|
23991
24608
|
instance: instance2,
|
|
23992
24609
|
port
|
|
23993
24610
|
};
|
|
23994
24611
|
}
|
|
23995
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
24612
|
+
const instances = listLiveInstances().filter((instance2) => resolve13(instance2.cwd) === resolve13(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
23996
24613
|
if (instances.length !== 1)
|
|
23997
24614
|
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>.");
|
|
23998
24615
|
const [instance] = instances;
|
|
@@ -24010,7 +24627,7 @@ var waitForIosHmrClient = async (options) => {
|
|
|
24010
24627
|
return;
|
|
24011
24628
|
});
|
|
24012
24629
|
const status = response?.ok ? await response.json().catch(() => null) : null;
|
|
24013
|
-
const targets =
|
|
24630
|
+
const targets = isRecord7(status) && isRecord7(status.connectedTargets) ? status.connectedTargets : undefined;
|
|
24014
24631
|
if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
|
|
24015
24632
|
return;
|
|
24016
24633
|
await Bun.sleep(100);
|
|
@@ -24082,11 +24699,11 @@ var physicalIosCapture = async (options) => {
|
|
|
24082
24699
|
remote: false,
|
|
24083
24700
|
xcodeVersion: requireCapturedCommand(["/usr/bin/xcodebuild", "-version"], "Xcode version inspection").stdout.trim(),
|
|
24084
24701
|
xcrun: "/usr/bin/xcrun",
|
|
24085
|
-
capture: async (command) =>
|
|
24702
|
+
capture: async (command) => captureCommand3(command)
|
|
24086
24703
|
};
|
|
24087
24704
|
};
|
|
24088
24705
|
var selectIosSimulator = (xcrun, explicitUdid) => {
|
|
24089
|
-
const result =
|
|
24706
|
+
const result = captureCommand3([
|
|
24090
24707
|
xcrun,
|
|
24091
24708
|
"simctl",
|
|
24092
24709
|
"list",
|
|
@@ -24109,15 +24726,15 @@ var selectIosSimulator = (xcrun, explicitUdid) => {
|
|
|
24109
24726
|
return selected;
|
|
24110
24727
|
};
|
|
24111
24728
|
var requireCapturedCommand = (command, label) => {
|
|
24112
|
-
const result =
|
|
24729
|
+
const result = captureCommand3(command);
|
|
24113
24730
|
if (result.exitCode !== 0)
|
|
24114
24731
|
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
|
|
24115
24732
|
return result;
|
|
24116
24733
|
};
|
|
24117
24734
|
var writeIosFailureArtifacts = async (options) => {
|
|
24118
|
-
await
|
|
24119
|
-
const screenshot =
|
|
24120
|
-
const screenshotResult =
|
|
24735
|
+
await mkdir13(options.artifactRoot, { recursive: true });
|
|
24736
|
+
const screenshot = join14(options.artifactRoot, "ios-failure.png");
|
|
24737
|
+
const screenshotResult = captureCommand3([
|
|
24121
24738
|
options.xcrun,
|
|
24122
24739
|
"simctl",
|
|
24123
24740
|
"io",
|
|
@@ -24125,8 +24742,8 @@ var writeIosFailureArtifacts = async (options) => {
|
|
|
24125
24742
|
"screenshot",
|
|
24126
24743
|
screenshot
|
|
24127
24744
|
]);
|
|
24128
|
-
const diagnosticPath =
|
|
24129
|
-
await
|
|
24745
|
+
const diagnosticPath = join14(options.artifactRoot, "ios-failure.json");
|
|
24746
|
+
await writeFile15(diagnosticPath, `${JSON.stringify({
|
|
24130
24747
|
appId: options.appId,
|
|
24131
24748
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
24132
24749
|
platform: "ios",
|
|
@@ -24153,8 +24770,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
|
|
|
24153
24770
|
var absolutejsVersionForReport = async () => {
|
|
24154
24771
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
24155
24772
|
const versions = await Promise.all([
|
|
24156
|
-
|
|
24157
|
-
|
|
24773
|
+
resolve13(import.meta.dir, "..", "..", "package.json"),
|
|
24774
|
+
resolve13(import.meta.dir, "..", "..", "..", "package.json")
|
|
24158
24775
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
24159
24776
|
for (const version of versions) {
|
|
24160
24777
|
if (version === "unknown")
|
|
@@ -24165,7 +24782,7 @@ var absolutejsVersionForReport = async () => {
|
|
|
24165
24782
|
return absolutejsVersion;
|
|
24166
24783
|
};
|
|
24167
24784
|
var writeRequestedAndroidReport = async (options) => {
|
|
24168
|
-
const reportRoot = nativeReportRoot(options.args, options.projectRoot, "android");
|
|
24785
|
+
const reportRoot = options.reportRoot ?? nativeReportRoot(options.args, options.projectRoot, "android");
|
|
24169
24786
|
if (!reportRoot)
|
|
24170
24787
|
return;
|
|
24171
24788
|
const adbVersion = requireCapturedCommand([options.adb, "version"], "ADB version inspection").stdout.trim();
|
|
@@ -24174,6 +24791,7 @@ var writeRequestedAndroidReport = async (options) => {
|
|
|
24174
24791
|
adbVersion,
|
|
24175
24792
|
bunVersion: Bun.version,
|
|
24176
24793
|
host: `${process.platform}-${process.arch}`,
|
|
24794
|
+
...options.provider ? { provider: options.provider } : {},
|
|
24177
24795
|
run: options.run
|
|
24178
24796
|
});
|
|
24179
24797
|
const paths = await writeAbsoluteNativeTestReport(reportRoot, report);
|
|
@@ -24378,8 +24996,8 @@ var testIos = async (args) => {
|
|
|
24378
24996
|
mobile.appId
|
|
24379
24997
|
], "iOS app launch");
|
|
24380
24998
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
24381
|
-
await
|
|
24382
|
-
const screenshot =
|
|
24999
|
+
await mkdir13(artifactRoot, { recursive: true });
|
|
25000
|
+
const screenshot = join14(artifactRoot, "ios-simulator.png");
|
|
24383
25001
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
24384
25002
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
24385
25003
|
const report = {
|
|
@@ -24565,7 +25183,7 @@ var runMobile = async (args) => {
|
|
|
24565
25183
|
await publishIos(args.slice(2));
|
|
24566
25184
|
return;
|
|
24567
25185
|
}
|
|
24568
|
-
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 [--route path
|
|
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]");
|
|
24569
25187
|
};
|
|
24570
25188
|
export {
|
|
24571
25189
|
runMobile
|