@absolutejs/absolute 0.20.0-beta.105 → 0.20.0-beta.107
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/README.md +6 -0
- package/changelog.json +50 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/{dev-9cp5t2n1.js → dev-f6b6hkke.js} +1 -1
- package/dist/cli/{index-7fxrgx5q.js → index-jf1f6npt.js} +232 -3
- package/dist/cli/index.js +2 -2
- package/dist/cli/{mobile-fbtq3s5x.js → mobile-7v8hfmdq.js} +812 -189
- package/dist/cli/{remoteMacProtocol-bbramzh6.js → remoteMacProtocol-dybpgazw.js} +5 -1
- package/dist/mobile/index.js +762 -101
- package/dist/mobile/index.js.map +8 -7
- package/dist/mobile/remoteMacAgentEntry.js +295 -295
- package/dist/src/mobile/index.d.ts +1 -0
- package/dist/src/mobile/iosReleaseAcceptance.d.ts +3 -0
- package/dist/src/mobile/nativeTestReport.d.ts +1 -0
- package/dist/src/mobile/releaseCertification.d.ts +59 -0
- package/dist/src/mobile/remoteMacProtocol.d.ts +44 -3
- package/dist/src/mobile/remoteMacWire.d.ts +1 -1
- package/package.json +1 -1
|
@@ -29,8 +29,9 @@ import {
|
|
|
29
29
|
inspectAbsoluteRemoteMacWorkspace,
|
|
30
30
|
listAbsoluteRemoteMacProfiles,
|
|
31
31
|
pairAbsoluteRemoteMac,
|
|
32
|
-
removeAbsoluteRemoteMacProfile
|
|
33
|
-
|
|
32
|
+
removeAbsoluteRemoteMacProfile,
|
|
33
|
+
runAbsoluteRemoteIosReleaseAcceptance
|
|
34
|
+
} from "./index-jf1f6npt.js";
|
|
34
35
|
import {
|
|
35
36
|
ABSOLUTE_IOS_SIMULATOR_NAME,
|
|
36
37
|
parseIosSimulators,
|
|
@@ -4414,9 +4415,9 @@ var require_sha256 = __commonJS(function(exports, module) {
|
|
|
4414
4415
|
var forge = require_forge();
|
|
4415
4416
|
require_md();
|
|
4416
4417
|
require_util();
|
|
4417
|
-
var
|
|
4418
|
-
forge.md.sha256 = forge.md.algorithms.sha256 =
|
|
4419
|
-
|
|
4418
|
+
var sha2565 = module.exports = forge.sha256 = forge.sha256 || {};
|
|
4419
|
+
forge.md.sha256 = forge.md.algorithms.sha256 = sha2565;
|
|
4420
|
+
sha2565.create = function() {
|
|
4420
4421
|
if (!_initialized) {
|
|
4421
4422
|
_init();
|
|
4422
4423
|
}
|
|
@@ -16778,15 +16779,15 @@ var require_main = __commonJS(function(exports) {
|
|
|
16778
16779
|
|
|
16779
16780
|
// src/cli/scripts/mobile.ts
|
|
16780
16781
|
import {
|
|
16781
|
-
access as
|
|
16782
|
-
mkdir as
|
|
16783
|
-
mkdtemp as
|
|
16784
|
-
readFile as
|
|
16785
|
-
rm as
|
|
16786
|
-
writeFile as
|
|
16782
|
+
access as access15,
|
|
16783
|
+
mkdir as mkdir16,
|
|
16784
|
+
mkdtemp as mkdtemp6,
|
|
16785
|
+
readFile as readFile20,
|
|
16786
|
+
rm as rm9,
|
|
16787
|
+
writeFile as writeFile17
|
|
16787
16788
|
} from "fs/promises";
|
|
16788
16789
|
import { createPublicKey as createPublicKey2 } from "crypto";
|
|
16789
|
-
import { join as
|
|
16790
|
+
import { join as join17, relative as relative14, resolve as resolve15 } from "path";
|
|
16790
16791
|
import { createInterface } from "readline/promises";
|
|
16791
16792
|
|
|
16792
16793
|
// src/mobile/nativeDeepLinks.ts
|
|
@@ -21241,6 +21242,7 @@ var defaultRun2 = async (command, options = {}) => {
|
|
|
21241
21242
|
const process2 = Bun.spawn(command, {
|
|
21242
21243
|
cwd: options.cwd,
|
|
21243
21244
|
env: options.env,
|
|
21245
|
+
signal: options.signal,
|
|
21244
21246
|
stderr: "pipe",
|
|
21245
21247
|
stdin: "ignore",
|
|
21246
21248
|
stdout: "pipe"
|
|
@@ -21328,6 +21330,7 @@ var releaseMarker = (release) => {
|
|
|
21328
21330
|
var waitForSimulatorMarker = async (options) => {
|
|
21329
21331
|
const startedAt = performance.now();
|
|
21330
21332
|
const poll = async () => {
|
|
21333
|
+
options.signal?.throwIfAborted();
|
|
21331
21334
|
if (performance.now() - startedAt >= READY_TIMEOUT_MS)
|
|
21332
21335
|
throw new Error("The installed iOS Release app launched but its embedded content did not become ready.");
|
|
21333
21336
|
const logs = await options.run([
|
|
@@ -21366,7 +21369,12 @@ var launchSimulator = async (options) => {
|
|
|
21366
21369
|
return Math.round(performance.now() - startedAt);
|
|
21367
21370
|
};
|
|
21368
21371
|
var runAbsoluteIosSimulatorReleaseAcceptance = async (options) => {
|
|
21369
|
-
const
|
|
21372
|
+
const baseRun = options.run ?? defaultRun2;
|
|
21373
|
+
const run = (command, commandOptions = {}) => baseRun(command, {
|
|
21374
|
+
...commandOptions,
|
|
21375
|
+
...options.signal ? { signal: options.signal } : {}
|
|
21376
|
+
});
|
|
21377
|
+
options.signal?.throwIfAborted();
|
|
21370
21378
|
const xcodebuild = options.xcodebuild ?? "/usr/bin/xcodebuild";
|
|
21371
21379
|
const xcrun = options.xcrun ?? "/usr/bin/xcrun";
|
|
21372
21380
|
const startedAt = performance.now();
|
|
@@ -21464,7 +21472,12 @@ var observeDeviceLaunch = async (options) => {
|
|
|
21464
21472
|
"--device",
|
|
21465
21473
|
options.device,
|
|
21466
21474
|
options.appId
|
|
21467
|
-
], {
|
|
21475
|
+
], {
|
|
21476
|
+
signal: options.signal,
|
|
21477
|
+
stderr: "pipe",
|
|
21478
|
+
stdin: "ignore",
|
|
21479
|
+
stdout: "pipe"
|
|
21480
|
+
});
|
|
21468
21481
|
const stdoutReader = process2.stdout.getReader();
|
|
21469
21482
|
const stderrReader = process2.stderr.getReader();
|
|
21470
21483
|
const requireMarker = async (reader) => {
|
|
@@ -21551,7 +21564,12 @@ var inspectTestFlightInstallation = async (options) => {
|
|
|
21551
21564
|
var runAbsoluteIosDeviceReleaseAcceptance = async (options) => {
|
|
21552
21565
|
if (!options.networkUnavailableConfirmed)
|
|
21553
21566
|
throw new TypeError("Physical iOS offline acceptance requires explicit confirmation that Airplane Mode is enabled and Wi-Fi is disabled in Settings.");
|
|
21554
|
-
const
|
|
21567
|
+
const baseRun = options.run ?? defaultRun2;
|
|
21568
|
+
const run = (command, commandOptions = {}) => baseRun(command, {
|
|
21569
|
+
...commandOptions,
|
|
21570
|
+
...options.signal ? { signal: options.signal } : {}
|
|
21571
|
+
});
|
|
21572
|
+
options.signal?.throwIfAborted();
|
|
21555
21573
|
const xcrun = options.xcrun ?? "/usr/bin/xcrun";
|
|
21556
21574
|
const startedAt = performance.now();
|
|
21557
21575
|
await requireSuccess2(run, [
|
|
@@ -21570,7 +21588,11 @@ var runAbsoluteIosDeviceReleaseAcceptance = async (options) => {
|
|
|
21570
21588
|
xcrun
|
|
21571
21589
|
};
|
|
21572
21590
|
const installMs = options.distribution === "registered-device" ? await installRegisteredDeviceRelease(installation) : await inspectTestFlightInstallation(installation);
|
|
21573
|
-
const observe = options.observeLaunch ?? ((launch2) => observeDeviceLaunch({
|
|
21591
|
+
const observe = options.observeLaunch ?? ((launch2) => observeDeviceLaunch({
|
|
21592
|
+
...launch2,
|
|
21593
|
+
...options.signal ? { signal: options.signal } : {},
|
|
21594
|
+
xcrun
|
|
21595
|
+
}));
|
|
21574
21596
|
const marker = releaseMarker(options.release);
|
|
21575
21597
|
const launchMs = await observe({
|
|
21576
21598
|
appId: options.release.metadata.appId,
|
|
@@ -21776,7 +21798,7 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
|
|
|
21776
21798
|
if (run.iosRelease) {
|
|
21777
21799
|
const release = run.iosRelease;
|
|
21778
21800
|
checks.push({
|
|
21779
|
-
details: `Validated immutable release ${release.releaseId} (${release.artifactBytes} bytes, SHA-256 ${release.artifactSha256}); ${release.distribution} evidence is ${release.artifactExactness}, installed in ${release.installMs}ms.`,
|
|
21801
|
+
details: `Validated immutable release ${release.releaseId} (${release.artifactBytes} bytes, SHA-256 ${release.artifactSha256}); ${release.distribution} evidence is ${release.artifactExactness}, executed on the ${release.remote ? "paired Remote Mac" : "local Mac"}, and installed in ${release.installMs}ms.`,
|
|
21780
21802
|
id: "AUTO-IOS-RELEASE-01",
|
|
21781
21803
|
result: run.status === "pass" ? "PASS" : "FAIL"
|
|
21782
21804
|
}, {
|
|
@@ -21784,6 +21806,12 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
|
|
|
21784
21806
|
id: "AUTO-IOS-RELEASE-OFFLINE-01",
|
|
21785
21807
|
result: release.networkUnavailable === "user-confirmed" ? "PASS" : "SKIPPED"
|
|
21786
21808
|
});
|
|
21809
|
+
if (release.remote)
|
|
21810
|
+
checks.push({
|
|
21811
|
+
details: "The installed-release lane executed through the paired Remote Mac protocol; immutable inputs and returned evidence were verified on the initiating host.",
|
|
21812
|
+
id: "AUTO-IOS-REMOTE-01",
|
|
21813
|
+
result: run.status === "pass" ? "PASS" : "FAIL"
|
|
21814
|
+
});
|
|
21787
21815
|
}
|
|
21788
21816
|
if (run.upgrade) {
|
|
21789
21817
|
const { upgrade } = run;
|
|
@@ -22142,9 +22170,413 @@ var createAbsoluteAndroidTestReport = (options) => {
|
|
|
22142
22170
|
});
|
|
22143
22171
|
};
|
|
22144
22172
|
|
|
22173
|
+
// src/mobile/releaseCertification.ts
|
|
22174
|
+
import { createHash as createHash5 } from "crypto";
|
|
22175
|
+
import {
|
|
22176
|
+
access as access8,
|
|
22177
|
+
mkdir as mkdir11,
|
|
22178
|
+
mkdtemp as mkdtemp4,
|
|
22179
|
+
readFile as readFile15,
|
|
22180
|
+
rename as rename10,
|
|
22181
|
+
rm as rm7,
|
|
22182
|
+
writeFile as writeFile12
|
|
22183
|
+
} from "fs/promises";
|
|
22184
|
+
import { basename as basename3, isAbsolute as isAbsolute5, join as join13, relative as relative7, resolve as resolve8, sep as sep5 } from "path";
|
|
22185
|
+
var ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT = 1;
|
|
22186
|
+
var CERTIFICATION_PREFIX = "amobile_cert_";
|
|
22187
|
+
var SHA256 = /^[a-f0-9]{64}$/u;
|
|
22188
|
+
var STRENGTH_RANK = {
|
|
22189
|
+
device: 1,
|
|
22190
|
+
installed: 0,
|
|
22191
|
+
simulator: 0,
|
|
22192
|
+
store: 2
|
|
22193
|
+
};
|
|
22194
|
+
var isRequirement = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
|
|
22195
|
+
var isStrength = (value) => value === "installed" || value === "simulator" || value === "device" || value === "store";
|
|
22196
|
+
var isArtifactExactness = (value) => value === "archive-equivalent" || value === "source-equivalent" || value === "store-delivered";
|
|
22197
|
+
var isDistribution = (value) => value === "apple-processed" || value === "registered-device" || value === "simulator-release";
|
|
22198
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22199
|
+
var inside3 = (root, candidate) => {
|
|
22200
|
+
const path = relative7(resolve8(root), resolve8(candidate));
|
|
22201
|
+
return path === "" || !isAbsolute5(path) && path !== ".." && !path.startsWith(`..${sep5}`);
|
|
22202
|
+
};
|
|
22203
|
+
var exists4 = async (path) => {
|
|
22204
|
+
try {
|
|
22205
|
+
await access8(path);
|
|
22206
|
+
return true;
|
|
22207
|
+
} catch {
|
|
22208
|
+
return false;
|
|
22209
|
+
}
|
|
22210
|
+
};
|
|
22211
|
+
var sha2563 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
22212
|
+
var requireString3 = (value, name) => {
|
|
22213
|
+
if (typeof value !== "string" || !value)
|
|
22214
|
+
throw new TypeError(`Mobile certification ${name} is invalid.`);
|
|
22215
|
+
return value;
|
|
22216
|
+
};
|
|
22217
|
+
var requirePositiveInteger = (value, name) => {
|
|
22218
|
+
if (!Number.isSafeInteger(value) || Number(value) < 1)
|
|
22219
|
+
throw new TypeError(`Mobile certification ${name} is invalid.`);
|
|
22220
|
+
return Number(value);
|
|
22221
|
+
};
|
|
22222
|
+
var resolveEvidencePath = async (projectRoot, requested) => {
|
|
22223
|
+
const selected = resolve8(projectRoot, requested);
|
|
22224
|
+
if (!inside3(projectRoot, selected))
|
|
22225
|
+
throw new TypeError("Mobile certification evidence must remain inside the project.");
|
|
22226
|
+
if (basename3(selected) === "report.json")
|
|
22227
|
+
return selected;
|
|
22228
|
+
const report = join13(selected, "report.json");
|
|
22229
|
+
if (!await exists4(report))
|
|
22230
|
+
throw new TypeError(`Mobile certification evidence does not contain report.json: ${requested}`);
|
|
22231
|
+
return report;
|
|
22232
|
+
};
|
|
22233
|
+
var releaseIdentity = (release) => {
|
|
22234
|
+
const { metadata } = release;
|
|
22235
|
+
if (!metadata.signed)
|
|
22236
|
+
throw new TypeError("Mobile release certification requires a signed native release.");
|
|
22237
|
+
const common = {
|
|
22238
|
+
appBuild: metadata.appBuild,
|
|
22239
|
+
appId: metadata.appId,
|
|
22240
|
+
artifactBytes: metadata.bytes,
|
|
22241
|
+
artifactSha256: metadata.sha256,
|
|
22242
|
+
engine: metadata.engine,
|
|
22243
|
+
platform: metadata.platform,
|
|
22244
|
+
releaseId: metadata.releaseId,
|
|
22245
|
+
runtime: metadata.runtime,
|
|
22246
|
+
signed: true
|
|
22247
|
+
};
|
|
22248
|
+
if (metadata.platform === "android")
|
|
22249
|
+
return {
|
|
22250
|
+
...common,
|
|
22251
|
+
...metadata.versionCode === undefined ? {} : { versionCode: metadata.versionCode }
|
|
22252
|
+
};
|
|
22253
|
+
return {
|
|
22254
|
+
...common,
|
|
22255
|
+
...metadata.buildNumber === undefined ? {} : { buildNumber: metadata.buildNumber },
|
|
22256
|
+
marketingVersion: metadata.marketingVersion
|
|
22257
|
+
};
|
|
22258
|
+
};
|
|
22259
|
+
var requireCheck = (report, id, accepted = ["PASS"]) => {
|
|
22260
|
+
const checks = report.automatedChecks;
|
|
22261
|
+
if (!Array.isArray(checks))
|
|
22262
|
+
throw new TypeError("Mobile certification evidence has invalid automated checks.");
|
|
22263
|
+
const check = checks.find((candidate) => isRecord6(candidate) && candidate.id === id);
|
|
22264
|
+
if (!isRecord6(check) || !accepted.includes(String(check.result)))
|
|
22265
|
+
throw new TypeError(`Mobile certification evidence does not satisfy ${id}.`);
|
|
22266
|
+
};
|
|
22267
|
+
var validateCommonReport = (report, release) => {
|
|
22268
|
+
if (report.reportVersion !== 1 || report.platform !== release.platform || !isRecord6(report.metadata) || report.metadata.provider !== release.engine || !isRecord6(report.run) || report.run.status !== "pass" || report.run.appId !== release.appId)
|
|
22269
|
+
throw new TypeError("Mobile certification evidence does not match the release platform, provider, application, or successful-run contract.");
|
|
22270
|
+
requireCheck(report, "AUTO-DEV-01");
|
|
22271
|
+
return {
|
|
22272
|
+
generatedAt: requireString3(report.generatedAt, "evidence generatedAt"),
|
|
22273
|
+
run: report.run
|
|
22274
|
+
};
|
|
22275
|
+
};
|
|
22276
|
+
var validateReleaseFields = (value, release) => {
|
|
22277
|
+
if (value.releaseId !== release.releaseId || value.artifactSha256 !== release.artifactSha256 || value.artifactBytes !== release.artifactBytes || value.engine !== release.engine || value.signed !== true)
|
|
22278
|
+
throw new TypeError("Mobile certification evidence does not match the immutable release identity.");
|
|
22279
|
+
};
|
|
22280
|
+
var validateAndroidEvidence = (report, run, release, reportSha256, generatedAt) => {
|
|
22281
|
+
if (!isRecord6(run.release))
|
|
22282
|
+
throw new TypeError("Android certification evidence has no installed release result.");
|
|
22283
|
+
validateReleaseFields(run.release, release);
|
|
22284
|
+
if (run.release.embeddedOffline !== true)
|
|
22285
|
+
throw new TypeError("Android certification evidence did not prove embedded offline relaunch.");
|
|
22286
|
+
requireCheck(report, "AUTO-RELEASE-01");
|
|
22287
|
+
requireCheck(report, "AUTO-RELEASE-OFFLINE-01");
|
|
22288
|
+
return {
|
|
22289
|
+
generatedAt,
|
|
22290
|
+
networkUnavailable: "proven",
|
|
22291
|
+
remote: false,
|
|
22292
|
+
reportSha256,
|
|
22293
|
+
strength: "installed"
|
|
22294
|
+
};
|
|
22295
|
+
};
|
|
22296
|
+
var validateIosEvidence = (report, run, release, reportSha256, generatedAt) => {
|
|
22297
|
+
if (!isRecord6(run.iosRelease))
|
|
22298
|
+
throw new TypeError("iOS certification evidence has no installed release result.");
|
|
22299
|
+
const ios = run.iosRelease;
|
|
22300
|
+
validateReleaseFields(ios, release);
|
|
22301
|
+
if (ios.embeddedLocal !== true)
|
|
22302
|
+
throw new TypeError("iOS certification evidence did not prove embedded local rendering.");
|
|
22303
|
+
requireCheck(report, "AUTO-IOS-RELEASE-01");
|
|
22304
|
+
let strength;
|
|
22305
|
+
if (ios.distribution === "simulator-release" && ios.artifactExactness === "source-equivalent" && run.targetKind === "simulator" && ios.networkUnavailable === "not-proven")
|
|
22306
|
+
strength = "simulator";
|
|
22307
|
+
else if (ios.distribution === "registered-device" && ios.artifactExactness === "archive-equivalent" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
|
|
22308
|
+
strength = "device";
|
|
22309
|
+
else if (ios.distribution === "apple-processed" && ios.artifactExactness === "store-delivered" && run.targetKind === "device" && ios.networkUnavailable === "user-confirmed")
|
|
22310
|
+
strength = "store";
|
|
22311
|
+
else
|
|
22312
|
+
throw new TypeError("iOS certification evidence contains an invalid target, distribution, exactness, or offline-strength combination.");
|
|
22313
|
+
if (strength === "simulator")
|
|
22314
|
+
requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01", ["SKIPPED"]);
|
|
22315
|
+
else
|
|
22316
|
+
requireCheck(report, "AUTO-IOS-RELEASE-OFFLINE-01");
|
|
22317
|
+
return {
|
|
22318
|
+
artifactExactness: ios.artifactExactness,
|
|
22319
|
+
distribution: ios.distribution,
|
|
22320
|
+
generatedAt,
|
|
22321
|
+
networkUnavailable: ios.networkUnavailable === "user-confirmed" ? "proven" : "not-proven",
|
|
22322
|
+
remote: ios.remote === true,
|
|
22323
|
+
reportSha256,
|
|
22324
|
+
strength
|
|
22325
|
+
};
|
|
22326
|
+
};
|
|
22327
|
+
var validateEvidence = async (projectRoot, requested, release) => {
|
|
22328
|
+
const path = await resolveEvidencePath(projectRoot, requested);
|
|
22329
|
+
const bytes = await readFile15(path);
|
|
22330
|
+
let value;
|
|
22331
|
+
try {
|
|
22332
|
+
value = JSON.parse(bytes.toString("utf8"));
|
|
22333
|
+
} catch {
|
|
22334
|
+
throw new TypeError("Mobile certification evidence report is not valid JSON.");
|
|
22335
|
+
}
|
|
22336
|
+
if (!isRecord6(value))
|
|
22337
|
+
throw new TypeError("Mobile certification evidence report must contain an object.");
|
|
22338
|
+
const { generatedAt, run } = validateCommonReport(value, release);
|
|
22339
|
+
const digest = sha2563(bytes);
|
|
22340
|
+
return release.platform === "android" ? validateAndroidEvidence(value, run, release, digest, generatedAt) : validateIosEvidence(value, run, release, digest, generatedAt);
|
|
22341
|
+
};
|
|
22342
|
+
var defaultRequirement = (platform) => platform === "android" ? "installed" : "simulator";
|
|
22343
|
+
var validateRequirement = (platform, requirement) => {
|
|
22344
|
+
if (platform === "android" && requirement !== "installed")
|
|
22345
|
+
throw new TypeError("Android certification currently uses its fixed installed/offline requirement; --require simulator, device, and store are iOS policies.");
|
|
22346
|
+
if (platform === "ios" && requirement === "installed")
|
|
22347
|
+
throw new TypeError("iOS certification requires simulator, device, or store evidence.");
|
|
22348
|
+
};
|
|
22349
|
+
var satisfiesRequirement = (strength, requirement) => {
|
|
22350
|
+
if (requirement === "installed")
|
|
22351
|
+
return strength === "installed";
|
|
22352
|
+
if (strength === "installed")
|
|
22353
|
+
return false;
|
|
22354
|
+
return STRENGTH_RANK[strength] >= STRENGTH_RANK[requirement];
|
|
22355
|
+
};
|
|
22356
|
+
var validateCertifiedEvidence = (platform, evidence) => {
|
|
22357
|
+
if (platform === "android") {
|
|
22358
|
+
if (evidence.strength !== "installed" || evidence.networkUnavailable !== "proven" || evidence.artifactExactness !== undefined || evidence.distribution !== undefined || evidence.remote)
|
|
22359
|
+
throw new TypeError("Android release certification contains invalid evidence strength.");
|
|
22360
|
+
return;
|
|
22361
|
+
}
|
|
22362
|
+
const simulator = evidence.strength === "simulator" && evidence.artifactExactness === "source-equivalent" && evidence.distribution === "simulator-release" && evidence.networkUnavailable === "not-proven";
|
|
22363
|
+
const device = evidence.strength === "device" && evidence.artifactExactness === "archive-equivalent" && evidence.distribution === "registered-device" && evidence.networkUnavailable === "proven";
|
|
22364
|
+
const store = evidence.strength === "store" && evidence.artifactExactness === "store-delivered" && evidence.distribution === "apple-processed" && evidence.networkUnavailable === "proven";
|
|
22365
|
+
if (!simulator && !device && !store)
|
|
22366
|
+
throw new TypeError("iOS release certification contains invalid evidence strength.");
|
|
22367
|
+
};
|
|
22368
|
+
var canonicalJsonValue = (value) => {
|
|
22369
|
+
if (Array.isArray(value))
|
|
22370
|
+
return value.map(canonicalJsonValue);
|
|
22371
|
+
if (!isRecord6(value))
|
|
22372
|
+
return value;
|
|
22373
|
+
const sorted = {};
|
|
22374
|
+
for (const key of Object.keys(value).sort())
|
|
22375
|
+
sorted[key] = canonicalJsonValue(value[key]);
|
|
22376
|
+
return sorted;
|
|
22377
|
+
};
|
|
22378
|
+
var certificationBody = (certification) => JSON.stringify(canonicalJsonValue(certification));
|
|
22379
|
+
var createAbsoluteMobileReleaseCertification = async (options) => {
|
|
22380
|
+
if (options.evidencePaths.length === 0)
|
|
22381
|
+
throw new TypeError("Mobile release certification requires at least one --evidence report.");
|
|
22382
|
+
const release = releaseIdentity(options.release);
|
|
22383
|
+
const requirement = options.requirement ?? defaultRequirement(release.platform);
|
|
22384
|
+
validateRequirement(release.platform, requirement);
|
|
22385
|
+
const evidence = await Promise.all(options.evidencePaths.map((path) => validateEvidence(options.projectRoot, path, release)));
|
|
22386
|
+
evidence.sort((left, right) => left.reportSha256.localeCompare(right.reportSha256));
|
|
22387
|
+
const [firstEvidence] = evidence;
|
|
22388
|
+
if (!firstEvidence)
|
|
22389
|
+
throw new TypeError("Mobile release certification requires valid evidence.");
|
|
22390
|
+
const strength = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, firstEvidence.strength);
|
|
22391
|
+
if (!satisfiesRequirement(strength, requirement))
|
|
22392
|
+
throw new TypeError(`Mobile release evidence strength ${strength} does not satisfy required ${requirement} certification.`);
|
|
22393
|
+
const body = {
|
|
22394
|
+
evidence,
|
|
22395
|
+
format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
|
|
22396
|
+
generatedAt: options.generatedAt ?? evidence.reduce((latest, candidate) => candidate.generatedAt > latest ? candidate.generatedAt : latest, firstEvidence.generatedAt),
|
|
22397
|
+
release,
|
|
22398
|
+
requirement,
|
|
22399
|
+
status: "certified",
|
|
22400
|
+
strength
|
|
22401
|
+
};
|
|
22402
|
+
return {
|
|
22403
|
+
certificationId: `${CERTIFICATION_PREFIX}${sha2563(certificationBody(body))}`,
|
|
22404
|
+
...body
|
|
22405
|
+
};
|
|
22406
|
+
};
|
|
22407
|
+
var parseCertification = (value) => {
|
|
22408
|
+
if (!isRecord6(value) || value.format !== ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT || value.status !== "certified" || !isRecord6(value.release) || !Array.isArray(value.evidence))
|
|
22409
|
+
throw new TypeError("Mobile release certification contract is invalid.");
|
|
22410
|
+
const { release } = value;
|
|
22411
|
+
const { platform } = release;
|
|
22412
|
+
const { engine } = release;
|
|
22413
|
+
if (platform !== "android" && platform !== "ios" || engine !== "capacitor" && engine !== "expo" || release.signed !== true)
|
|
22414
|
+
throw new TypeError("Mobile release certification identity is invalid.");
|
|
22415
|
+
const { requirement } = value;
|
|
22416
|
+
const { strength } = value;
|
|
22417
|
+
if (!isRequirement(requirement) || !isStrength(strength))
|
|
22418
|
+
throw new TypeError("Mobile release certification policy is invalid.");
|
|
22419
|
+
validateRequirement(platform, requirement);
|
|
22420
|
+
if (!satisfiesRequirement(strength, requirement))
|
|
22421
|
+
throw new TypeError("Mobile release certification does not satisfy its declared policy.");
|
|
22422
|
+
const artifactBytes = requirePositiveInteger(release.artifactBytes, "release artifactBytes");
|
|
22423
|
+
const artifactSha256 = requireString3(release.artifactSha256, "release artifactSha256");
|
|
22424
|
+
if (!SHA256.test(artifactSha256))
|
|
22425
|
+
throw new TypeError("Mobile release certification artifact digest is invalid.");
|
|
22426
|
+
const parsedRelease = {
|
|
22427
|
+
appBuild: requireString3(release.appBuild, "release appBuild"),
|
|
22428
|
+
appId: requireString3(release.appId, "release appId"),
|
|
22429
|
+
artifactBytes,
|
|
22430
|
+
artifactSha256,
|
|
22431
|
+
engine,
|
|
22432
|
+
platform,
|
|
22433
|
+
releaseId: requireString3(release.releaseId, "release releaseId"),
|
|
22434
|
+
runtime: requireString3(release.runtime, "release runtime"),
|
|
22435
|
+
signed: true,
|
|
22436
|
+
...release.buildNumber === undefined ? {} : {
|
|
22437
|
+
buildNumber: requirePositiveInteger(release.buildNumber, "release buildNumber")
|
|
22438
|
+
},
|
|
22439
|
+
...release.marketingVersion === undefined ? {} : {
|
|
22440
|
+
marketingVersion: requireString3(release.marketingVersion, "release marketingVersion")
|
|
22441
|
+
},
|
|
22442
|
+
...release.versionCode === undefined ? {} : {
|
|
22443
|
+
versionCode: requirePositiveInteger(release.versionCode, "release versionCode")
|
|
22444
|
+
}
|
|
22445
|
+
};
|
|
22446
|
+
const evidence = value.evidence.map((candidate) => {
|
|
22447
|
+
if (!isRecord6(candidate) || !SHA256.test(String(candidate.reportSha256)) || !isStrength(candidate.strength) || candidate.networkUnavailable !== "not-proven" && candidate.networkUnavailable !== "proven" || typeof candidate.remote !== "boolean" || typeof candidate.generatedAt !== "string" || candidate.artifactExactness !== undefined && !isArtifactExactness(candidate.artifactExactness) || candidate.distribution !== undefined && !isDistribution(candidate.distribution))
|
|
22448
|
+
throw new TypeError("Mobile release certification evidence is invalid.");
|
|
22449
|
+
const parsedEvidence = {
|
|
22450
|
+
generatedAt: candidate.generatedAt,
|
|
22451
|
+
networkUnavailable: candidate.networkUnavailable,
|
|
22452
|
+
remote: candidate.remote,
|
|
22453
|
+
reportSha256: String(candidate.reportSha256),
|
|
22454
|
+
strength: candidate.strength,
|
|
22455
|
+
...candidate.artifactExactness === undefined ? {} : {
|
|
22456
|
+
artifactExactness: candidate.artifactExactness
|
|
22457
|
+
},
|
|
22458
|
+
...candidate.distribution === undefined ? {} : { distribution: candidate.distribution }
|
|
22459
|
+
};
|
|
22460
|
+
validateCertifiedEvidence(platform, parsedEvidence);
|
|
22461
|
+
return parsedEvidence;
|
|
22462
|
+
});
|
|
22463
|
+
const strongestEvidence = evidence.reduce((strongest, candidate) => STRENGTH_RANK[candidate.strength] > STRENGTH_RANK[strongest] ? candidate.strength : strongest, evidence[0]?.strength ?? "installed");
|
|
22464
|
+
if (evidence.length === 0 || strongestEvidence !== strength)
|
|
22465
|
+
throw new TypeError("Mobile release certification evidence does not support its declared strength.");
|
|
22466
|
+
const parsed = {
|
|
22467
|
+
certificationId: requireString3(value.certificationId, "certificationId"),
|
|
22468
|
+
evidence,
|
|
22469
|
+
format: ABSOLUTE_MOBILE_RELEASE_CERTIFICATION_FORMAT,
|
|
22470
|
+
generatedAt: requireString3(value.generatedAt, "generatedAt"),
|
|
22471
|
+
release: parsedRelease,
|
|
22472
|
+
requirement,
|
|
22473
|
+
status: "certified",
|
|
22474
|
+
strength
|
|
22475
|
+
};
|
|
22476
|
+
const { certificationId, ...body } = parsed;
|
|
22477
|
+
if (certificationId !== `${CERTIFICATION_PREFIX}${sha2563(certificationBody(body))}`)
|
|
22478
|
+
throw new TypeError("Mobile release certification content digest is invalid.");
|
|
22479
|
+
return parsed;
|
|
22480
|
+
};
|
|
22481
|
+
var readAbsoluteMobileReleaseCertification = async (projectRoot, requested) => {
|
|
22482
|
+
const selected = resolve8(projectRoot, requested);
|
|
22483
|
+
if (!inside3(projectRoot, selected))
|
|
22484
|
+
throw new TypeError("Mobile release certification must remain inside the project.");
|
|
22485
|
+
const path = basename3(selected) === "certification.json" ? selected : join13(selected, "certification.json");
|
|
22486
|
+
const value = JSON.parse(await readFile15(path, "utf8"));
|
|
22487
|
+
return { certification: parseCertification(value), path };
|
|
22488
|
+
};
|
|
22489
|
+
var renderAbsoluteMobileReleaseCertification = (certification) => {
|
|
22490
|
+
const { release } = certification;
|
|
22491
|
+
const version = release.platform === "ios" ? `${release.marketingVersion ?? "unknown"} (${release.buildNumber ?? "unallocated"})` : String(release.versionCode ?? "unallocated");
|
|
22492
|
+
const evidence = certification.evidence.map((item) => `| ${item.strength} | ${item.networkUnavailable} | ${item.remote ? "paired Mac" : "local host"} | ${item.reportSha256} |`).join(`
|
|
22493
|
+
`);
|
|
22494
|
+
return `# AbsoluteJS mobile release certification
|
|
22495
|
+
|
|
22496
|
+
- Status: CERTIFIED
|
|
22497
|
+
- Certification: ${certification.certificationId}
|
|
22498
|
+
- Generated: ${certification.generatedAt}
|
|
22499
|
+
- Platform: ${release.platform}
|
|
22500
|
+
- Engine: ${release.engine}
|
|
22501
|
+
- Requirement: ${certification.requirement}
|
|
22502
|
+
- Evidence strength: ${certification.strength}
|
|
22503
|
+
- Release: ${release.releaseId}
|
|
22504
|
+
- Artifact SHA-256: ${release.artifactSha256}
|
|
22505
|
+
- Runtime fingerprint: ${release.runtime}
|
|
22506
|
+
- Embedded app build: ${release.appBuild}
|
|
22507
|
+
- Version/build: ${version}
|
|
22508
|
+
|
|
22509
|
+
This certification is local and contains no signing credentials, device identifiers, SSH destinations, report paths, application data, or page contents. Re-run verification against the immutable release before using it as a CI or promotion gate.
|
|
22510
|
+
|
|
22511
|
+
| Strength | Network unavailable | Execution | Report SHA-256 |
|
|
22512
|
+
| --- | --- | --- | --- |
|
|
22513
|
+
${evidence}
|
|
22514
|
+
`;
|
|
22515
|
+
};
|
|
22516
|
+
var verifyAbsoluteMobileReleaseCertification = (certification, release, requirement) => {
|
|
22517
|
+
const parsed = parseCertification(certification);
|
|
22518
|
+
const expected = releaseIdentity(release);
|
|
22519
|
+
if (JSON.stringify(parsed.release) !== JSON.stringify(expected))
|
|
22520
|
+
throw new TypeError("Mobile release certification was invalidated by a release identity, artifact, runtime, signing, version, build, or embedded-bundle change.");
|
|
22521
|
+
if (requirement) {
|
|
22522
|
+
validateRequirement(expected.platform, requirement);
|
|
22523
|
+
if (!satisfiesRequirement(parsed.strength, requirement))
|
|
22524
|
+
throw new TypeError(`Mobile release certification strength ${parsed.strength} does not satisfy required ${requirement} policy.`);
|
|
22525
|
+
}
|
|
22526
|
+
return parsed;
|
|
22527
|
+
};
|
|
22528
|
+
var writeAbsoluteMobileReleaseCertification = async (projectRoot, certification, outputDirectory) => {
|
|
22529
|
+
const root = resolve8(projectRoot);
|
|
22530
|
+
const parent = resolve8(root, outputDirectory ?? join13(".absolutejs", "mobile", "certifications", certification.release.platform, certification.release.releaseId));
|
|
22531
|
+
if (!inside3(root, parent))
|
|
22532
|
+
throw new TypeError("Mobile certification output must remain inside the project.");
|
|
22533
|
+
await mkdir11(parent, { recursive: true });
|
|
22534
|
+
const destination = join13(parent, certification.certificationId);
|
|
22535
|
+
const paths = {
|
|
22536
|
+
directory: destination,
|
|
22537
|
+
jsonPath: join13(destination, "certification.json"),
|
|
22538
|
+
markdownPath: join13(destination, "certification.md")
|
|
22539
|
+
};
|
|
22540
|
+
const json = `${JSON.stringify(certification, null, 2)}
|
|
22541
|
+
`;
|
|
22542
|
+
const markdown = renderAbsoluteMobileReleaseCertification(certification);
|
|
22543
|
+
const verifyExisting = async () => {
|
|
22544
|
+
const [existingJson, existingMarkdown] = await Promise.all([
|
|
22545
|
+
readFile15(paths.jsonPath, "utf8"),
|
|
22546
|
+
readFile15(paths.markdownPath, "utf8")
|
|
22547
|
+
]);
|
|
22548
|
+
if (existingJson !== json || existingMarkdown !== markdown)
|
|
22549
|
+
throw new TypeError(`Immutable mobile certification ${certification.certificationId} already exists with different content.`);
|
|
22550
|
+
};
|
|
22551
|
+
if (await exists4(destination)) {
|
|
22552
|
+
await verifyExisting();
|
|
22553
|
+
return paths;
|
|
22554
|
+
}
|
|
22555
|
+
const staging = await mkdtemp4(join13(parent, ".certification-"));
|
|
22556
|
+
try {
|
|
22557
|
+
await Promise.all([
|
|
22558
|
+
writeFile12(join13(staging, "certification.json"), json, {
|
|
22559
|
+
flag: "wx"
|
|
22560
|
+
}),
|
|
22561
|
+
writeFile12(join13(staging, "certification.md"), markdown, {
|
|
22562
|
+
flag: "wx"
|
|
22563
|
+
})
|
|
22564
|
+
]);
|
|
22565
|
+
await rename10(staging, destination).catch(async (error) => {
|
|
22566
|
+
if (await exists4(destination))
|
|
22567
|
+
return;
|
|
22568
|
+
throw error;
|
|
22569
|
+
});
|
|
22570
|
+
} finally {
|
|
22571
|
+
await rm7(staging, { force: true, recursive: true });
|
|
22572
|
+
}
|
|
22573
|
+
await verifyExisting();
|
|
22574
|
+
return paths;
|
|
22575
|
+
};
|
|
22576
|
+
|
|
22145
22577
|
// src/mobile/releasePublisher.ts
|
|
22146
|
-
import { access as
|
|
22147
|
-
import { isAbsolute as
|
|
22578
|
+
import { access as access9 } from "fs/promises";
|
|
22579
|
+
import { isAbsolute as isAbsolute6, relative as relative8, resolve as resolve9, sep as sep6 } from "path";
|
|
22148
22580
|
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
22149
22581
|
var prepareAbsoluteIosRelease = async (publisher, options) => {
|
|
22150
22582
|
if (typeof publisher.prepareIosRelease !== "function") {
|
|
@@ -22167,24 +22599,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
|
|
|
22167
22599
|
}
|
|
22168
22600
|
return versionCode;
|
|
22169
22601
|
};
|
|
22170
|
-
var
|
|
22171
|
-
var isPublisher = (value) =>
|
|
22602
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22603
|
+
var isPublisher = (value) => isRecord7(value) && typeof value.publish === "function";
|
|
22172
22604
|
var publisherModulePath = (projectRoot, requested) => {
|
|
22173
|
-
const root =
|
|
22174
|
-
const path =
|
|
22175
|
-
const projectRelative =
|
|
22176
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
22605
|
+
const root = resolve9(projectRoot);
|
|
22606
|
+
const path = resolve9(root, requested);
|
|
22607
|
+
const projectRelative = relative8(root, path);
|
|
22608
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
|
|
22177
22609
|
throw new TypeError("mobile publish --registry must remain inside the project.");
|
|
22178
22610
|
}
|
|
22179
22611
|
return path;
|
|
22180
22612
|
};
|
|
22181
22613
|
var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
|
|
22182
22614
|
const modulePath = publisherModulePath(projectRoot, requestedModulePath);
|
|
22183
|
-
await
|
|
22615
|
+
await access9(modulePath).catch(() => {
|
|
22184
22616
|
throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
|
|
22185
22617
|
});
|
|
22186
22618
|
const loaded = await import(pathToFileURL2(modulePath).href);
|
|
22187
|
-
const publisher =
|
|
22619
|
+
const publisher = isRecord7(loaded) ? loaded.default ?? loaded.registry : undefined;
|
|
22188
22620
|
if (!isPublisher(publisher)) {
|
|
22189
22621
|
throw new TypeError("Native release registry module must default-export a registry with publish(options).");
|
|
22190
22622
|
}
|
|
@@ -22242,8 +22674,8 @@ var publishAbsoluteIosRelease = async (options) => {
|
|
|
22242
22674
|
};
|
|
22243
22675
|
|
|
22244
22676
|
// src/mobile/mobileInspect.ts
|
|
22245
|
-
import { access as
|
|
22246
|
-
import { join as
|
|
22677
|
+
import { access as access10, readFile as readFile16 } from "fs/promises";
|
|
22678
|
+
import { join as join14, relative as relative9, resolve as resolve10 } from "path";
|
|
22247
22679
|
var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1;
|
|
22248
22680
|
var MOBILE_PACKAGE_NAMES = new Set([
|
|
22249
22681
|
"@absolutejs/absolute",
|
|
@@ -22258,19 +22690,19 @@ var MOBILE_PACKAGE_NAMES = new Set([
|
|
|
22258
22690
|
]);
|
|
22259
22691
|
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
22260
22692
|
var portablePath2 = (projectRoot, path) => {
|
|
22261
|
-
const value =
|
|
22693
|
+
const value = relative9(resolve10(projectRoot), resolve10(path)).replaceAll("\\", "/");
|
|
22262
22694
|
return value || ".";
|
|
22263
22695
|
};
|
|
22264
22696
|
var pathExists4 = async (path) => {
|
|
22265
22697
|
try {
|
|
22266
|
-
await
|
|
22698
|
+
await access10(path);
|
|
22267
22699
|
return true;
|
|
22268
22700
|
} catch {
|
|
22269
22701
|
return false;
|
|
22270
22702
|
}
|
|
22271
22703
|
};
|
|
22272
22704
|
var readObject2 = async (path) => {
|
|
22273
|
-
const value = JSON.parse(await
|
|
22705
|
+
const value = JSON.parse(await readFile16(path, "utf8"));
|
|
22274
22706
|
if (!isObject2(value))
|
|
22275
22707
|
throw new TypeError("JSON root must be an object.");
|
|
22276
22708
|
return value;
|
|
@@ -22282,13 +22714,13 @@ var addPackageDeclarations = (declarations, value) => {
|
|
|
22282
22714
|
declarations.set(name, declared);
|
|
22283
22715
|
};
|
|
22284
22716
|
var packageInspections = async (projectRoot, additionalNames) => {
|
|
22285
|
-
const project = await readObject2(
|
|
22717
|
+
const project = await readObject2(join14(projectRoot, "package.json"));
|
|
22286
22718
|
const declarations = new Map;
|
|
22287
22719
|
for (const field2 of ["dependencies", "devDependencies"])
|
|
22288
22720
|
addPackageDeclarations(declarations, project[field2]);
|
|
22289
22721
|
const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
|
|
22290
22722
|
return Promise.all(names.map(async (name) => {
|
|
22291
|
-
const installedManifest = await readObject2(
|
|
22723
|
+
const installedManifest = await readObject2(join14(projectRoot, "node_modules", name, "package.json")).catch(() => {
|
|
22292
22724
|
return;
|
|
22293
22725
|
});
|
|
22294
22726
|
const installed = installedManifest?.version;
|
|
@@ -22347,7 +22779,7 @@ var inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) =>
|
|
|
22347
22779
|
},
|
|
22348
22780
|
format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
|
|
22349
22781
|
nativeProjects: await Promise.all(config.platforms.map(async (platform) => {
|
|
22350
|
-
const path =
|
|
22782
|
+
const path = join14(config.nativeProjectDirectory, platform);
|
|
22351
22783
|
return {
|
|
22352
22784
|
initialized: await pathExists4(path),
|
|
22353
22785
|
path: portablePath2(projectRoot, path),
|
|
@@ -22404,8 +22836,8 @@ var renderAbsoluteMobileProjectInspection = (report) => {
|
|
|
22404
22836
|
|
|
22405
22837
|
// src/mobile/ciWorkflow.ts
|
|
22406
22838
|
import { existsSync } from "fs";
|
|
22407
|
-
import { access as
|
|
22408
|
-
import { dirname as dirname10, extname as extname2, relative as
|
|
22839
|
+
import { access as access11, mkdir as mkdir12, readFile as readFile17, writeFile as writeFile13 } from "fs/promises";
|
|
22840
|
+
import { dirname as dirname10, extname as extname2, relative as relative10, resolve as resolve11, sep as sep7 } from "path";
|
|
22409
22841
|
var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
|
|
22410
22842
|
var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
|
|
22411
22843
|
var CI_ENV_INDENTATION = 6;
|
|
@@ -22424,9 +22856,9 @@ var RESERVED_SECRET_NAMES = new Set([
|
|
|
22424
22856
|
"APP_STORE_CONNECT_KEY_ID",
|
|
22425
22857
|
"APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
|
|
22426
22858
|
]);
|
|
22427
|
-
var
|
|
22859
|
+
var exists5 = async (path) => {
|
|
22428
22860
|
try {
|
|
22429
|
-
await
|
|
22861
|
+
await access11(path);
|
|
22430
22862
|
return true;
|
|
22431
22863
|
} catch {
|
|
22432
22864
|
return false;
|
|
@@ -22434,10 +22866,10 @@ var exists4 = async (path) => {
|
|
|
22434
22866
|
};
|
|
22435
22867
|
var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
|
|
22436
22868
|
var projectPath2 = (projectRoot, value, field2, options = {}) => {
|
|
22437
|
-
const root =
|
|
22438
|
-
const path =
|
|
22439
|
-
const portable =
|
|
22440
|
-
if (portable === ".." || portable.startsWith(`..${
|
|
22869
|
+
const root = resolve11(projectRoot);
|
|
22870
|
+
const path = resolve11(root, value);
|
|
22871
|
+
const portable = relative10(root, path).replaceAll("\\", "/");
|
|
22872
|
+
if (portable === ".." || portable.startsWith(`..${sep7}`) || portable.startsWith("../") || portable === "") {
|
|
22441
22873
|
throw new TypeError(`${field2} must remain inside the project root.`);
|
|
22442
22874
|
}
|
|
22443
22875
|
if (/\r|\n/u.test(portable) || portable.startsWith("-"))
|
|
@@ -22447,11 +22879,11 @@ var projectPath2 = (projectRoot, value, field2, options = {}) => {
|
|
|
22447
22879
|
return portable;
|
|
22448
22880
|
};
|
|
22449
22881
|
var workflowOutputPath = (projectRoot, value) => {
|
|
22450
|
-
const root =
|
|
22451
|
-
const workflows =
|
|
22452
|
-
const path =
|
|
22453
|
-
const portable =
|
|
22454
|
-
if (portable === ".." || portable.startsWith(`..${
|
|
22882
|
+
const root = resolve11(projectRoot);
|
|
22883
|
+
const workflows = resolve11(root, ".github/workflows");
|
|
22884
|
+
const path = resolve11(root, value ?? ".github/workflows/absolute-mobile.yml");
|
|
22885
|
+
const portable = relative10(workflows, path);
|
|
22886
|
+
if (portable === ".." || portable.startsWith(`..${sep7}`) || extname2(path) !== ".yml" && extname2(path) !== ".yaml") {
|
|
22455
22887
|
throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
|
|
22456
22888
|
}
|
|
22457
22889
|
return path;
|
|
@@ -22880,12 +23312,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
|
|
|
22880
23312
|
var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
22881
23313
|
const path = workflowOutputPath(options.projectRoot, options.outputPath);
|
|
22882
23314
|
const generated = createAbsoluteMobileGithubWorkflow(options);
|
|
22883
|
-
const previous = await
|
|
23315
|
+
const previous = await exists5(path) ? await readFile17(path, "utf8") : undefined;
|
|
22884
23316
|
if (previous !== undefined && previous !== generated.workflow && !options.force)
|
|
22885
|
-
throw new TypeError(`${
|
|
23317
|
+
throw new TypeError(`${relative10(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
|
|
22886
23318
|
if (previous !== generated.workflow) {
|
|
22887
|
-
await
|
|
22888
|
-
await
|
|
23319
|
+
await mkdir12(dirname10(path), { recursive: true });
|
|
23320
|
+
await writeFile13(path, generated.workflow);
|
|
22889
23321
|
}
|
|
22890
23322
|
return {
|
|
22891
23323
|
changed: previous !== generated.workflow,
|
|
@@ -22898,46 +23330,46 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
|
|
|
22898
23330
|
};
|
|
22899
23331
|
|
|
22900
23332
|
// src/mobile/updateSigning.ts
|
|
22901
|
-
import { createHash as
|
|
23333
|
+
import { createHash as createHash6, sign, verify } from "crypto";
|
|
22902
23334
|
import {
|
|
22903
23335
|
cp,
|
|
22904
|
-
mkdir as
|
|
22905
|
-
mkdtemp as
|
|
23336
|
+
mkdir as mkdir13,
|
|
23337
|
+
mkdtemp as mkdtemp5,
|
|
22906
23338
|
readdir as readdir6,
|
|
22907
|
-
readFile as
|
|
22908
|
-
rename as
|
|
22909
|
-
rm as
|
|
23339
|
+
readFile as readFile18,
|
|
23340
|
+
rename as rename11,
|
|
23341
|
+
rm as rm8,
|
|
22910
23342
|
stat as stat6,
|
|
22911
|
-
writeFile as
|
|
23343
|
+
writeFile as writeFile14
|
|
22912
23344
|
} from "fs/promises";
|
|
22913
|
-
import { dirname as dirname11, join as
|
|
23345
|
+
import { dirname as dirname11, join as join15, relative as relative11, resolve as resolve12 } from "path";
|
|
22914
23346
|
var UPDATE_MANIFEST_FILE = "update.json";
|
|
22915
23347
|
var UPDATE_FILES_DIRECTORY = "files";
|
|
22916
|
-
var
|
|
23348
|
+
var sha2564 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
22917
23349
|
var listFiles = async (root, directory = root) => {
|
|
22918
23350
|
const entries = await readdir6(directory, { withFileTypes: true });
|
|
22919
23351
|
const paths = await Promise.all(entries.map(async (entry) => {
|
|
22920
|
-
const path =
|
|
23352
|
+
const path = join15(directory, entry.name);
|
|
22921
23353
|
if (entry.isDirectory())
|
|
22922
23354
|
return listFiles(root, path);
|
|
22923
23355
|
if (!entry.isFile())
|
|
22924
23356
|
throw new TypeError("Mobile updates cannot contain links or special files.");
|
|
22925
|
-
return [
|
|
23357
|
+
return [relative11(root, path).replaceAll("\\", "/")];
|
|
22926
23358
|
}));
|
|
22927
23359
|
return paths.flat().sort((left, right) => left.localeCompare(right));
|
|
22928
23360
|
};
|
|
22929
23361
|
var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
|
|
22930
|
-
const source =
|
|
23362
|
+
const source = join15(root, path);
|
|
22931
23363
|
const [metadata, contents] = await Promise.all([
|
|
22932
23364
|
stat6(source),
|
|
22933
|
-
|
|
23365
|
+
readFile18(source)
|
|
22934
23366
|
]);
|
|
22935
|
-
return { bytes: metadata.size, path, sha256:
|
|
23367
|
+
return { bytes: metadata.size, path, sha256: sha2564(contents) };
|
|
22936
23368
|
}));
|
|
22937
|
-
var releaseIdFor = (value) => `amu_${
|
|
23369
|
+
var releaseIdFor = (value) => `amu_${sha2564(canonicalizeAbsoluteMobileUpdate(value))}`;
|
|
22938
23370
|
var buildAbsoluteMobileUpdate = async (options) => {
|
|
22939
|
-
const bundleDirectory =
|
|
22940
|
-
const outputRoot =
|
|
23371
|
+
const bundleDirectory = resolve12(options.bundleDirectory);
|
|
23372
|
+
const outputRoot = resolve12(options.outputDirectory);
|
|
22941
23373
|
if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
|
|
22942
23374
|
throw new TypeError("Mobile update output must be outside the embedded bundle.");
|
|
22943
23375
|
const paths = await listFiles(bundleDirectory);
|
|
@@ -22965,30 +23397,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
|
|
|
22965
23397
|
value: signature.toString("base64")
|
|
22966
23398
|
}
|
|
22967
23399
|
});
|
|
22968
|
-
await
|
|
22969
|
-
const outputDirectory =
|
|
22970
|
-
const staging = await
|
|
23400
|
+
await mkdir13(outputRoot, { recursive: true });
|
|
23401
|
+
const outputDirectory = join15(outputRoot, manifest.releaseId);
|
|
23402
|
+
const staging = await mkdtemp5(join15(outputRoot, ".stage-"));
|
|
22971
23403
|
try {
|
|
22972
|
-
await cp(bundleDirectory,
|
|
23404
|
+
await cp(bundleDirectory, join15(staging, UPDATE_FILES_DIRECTORY), {
|
|
22973
23405
|
force: true,
|
|
22974
23406
|
recursive: true
|
|
22975
23407
|
});
|
|
22976
|
-
await
|
|
23408
|
+
await writeFile14(join15(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
|
|
22977
23409
|
`);
|
|
22978
|
-
await
|
|
23410
|
+
await rename11(staging, outputDirectory);
|
|
22979
23411
|
} catch (error) {
|
|
22980
|
-
await
|
|
23412
|
+
await rm8(staging, { force: true, recursive: true });
|
|
22981
23413
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
|
|
22982
23414
|
throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
|
|
22983
23415
|
throw error;
|
|
22984
23416
|
}
|
|
22985
23417
|
return {
|
|
22986
23418
|
manifest,
|
|
22987
|
-
manifestPath:
|
|
23419
|
+
manifestPath: join15(outputDirectory, UPDATE_MANIFEST_FILE),
|
|
22988
23420
|
outputDirectory
|
|
22989
23421
|
};
|
|
22990
23422
|
};
|
|
22991
|
-
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await
|
|
23423
|
+
var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile18(join15(resolve12(directory), UPDATE_MANIFEST_FILE), "utf8")));
|
|
22992
23424
|
var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
22993
23425
|
const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
|
|
22994
23426
|
const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
|
|
@@ -22998,8 +23430,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
|
|
|
22998
23430
|
};
|
|
22999
23431
|
|
|
23000
23432
|
// src/mobile/expoUpdate.ts
|
|
23001
|
-
import { access as
|
|
23002
|
-
import { dirname as dirname12, extname as extname3, join as
|
|
23433
|
+
import { access as access12, mkdir as mkdir14, readFile as readFile19, writeFile as writeFile15 } from "fs/promises";
|
|
23434
|
+
import { dirname as dirname12, extname as extname3, join as join16 } from "path";
|
|
23003
23435
|
var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
|
|
23004
23436
|
var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
|
|
23005
23437
|
var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -23065,7 +23497,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
|
|
|
23065
23497
|
};
|
|
23066
23498
|
};
|
|
23067
23499
|
var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
23068
|
-
const metadata = JSON.parse(await
|
|
23500
|
+
const metadata = JSON.parse(await readFile19(join16(options.exportDirectory, "metadata.json"), "utf8"));
|
|
23069
23501
|
if (!object2(metadata) || !object2(metadata.fileMetadata))
|
|
23070
23502
|
throw new TypeError("Expo export metadata.json is invalid.");
|
|
23071
23503
|
const descriptor = parseAbsoluteExpoUpdateDescriptor({
|
|
@@ -23076,33 +23508,33 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
|
|
|
23076
23508
|
runtimeVersion: options.runtimeVersion
|
|
23077
23509
|
});
|
|
23078
23510
|
const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
|
|
23079
|
-
await Promise.all(referenced.map(({ path }) =>
|
|
23511
|
+
await Promise.all(referenced.map(({ path }) => access12(join16(options.exportDirectory, path)).catch(() => {
|
|
23080
23512
|
throw new TypeError(`Expo export metadata references missing asset ${path}.`);
|
|
23081
23513
|
})));
|
|
23082
|
-
const destination =
|
|
23083
|
-
await
|
|
23084
|
-
await
|
|
23514
|
+
const destination = join16(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
|
|
23515
|
+
await mkdir14(dirname12(destination), { recursive: true });
|
|
23516
|
+
await writeFile15(destination, `${JSON.stringify(descriptor, null, "\t")}
|
|
23085
23517
|
`);
|
|
23086
23518
|
return { descriptor, path: destination };
|
|
23087
23519
|
};
|
|
23088
23520
|
|
|
23089
23521
|
// src/mobile/expoCodeSigning.ts
|
|
23090
23522
|
var import_code_signing_certificates = __toESM(require_main(), 1);
|
|
23091
|
-
import { access as
|
|
23092
|
-
import { dirname as dirname13, isAbsolute as
|
|
23093
|
-
var
|
|
23094
|
-
var
|
|
23095
|
-
const location =
|
|
23096
|
-
return location === "" || location !== ".." && !location.startsWith(`..${
|
|
23523
|
+
import { access as access13, mkdir as mkdir15, writeFile as writeFile16 } from "fs/promises";
|
|
23524
|
+
import { dirname as dirname13, isAbsolute as isAbsolute7, relative as relative12, resolve as resolve13, sep as sep8 } from "path";
|
|
23525
|
+
var exists6 = async (path) => access13(path).then(() => true).catch(() => false);
|
|
23526
|
+
var inside4 = (root, path) => {
|
|
23527
|
+
const location = relative12(root, path);
|
|
23528
|
+
return location === "" || location !== ".." && !location.startsWith(`..${sep8}`) && !isAbsolute7(location);
|
|
23097
23529
|
};
|
|
23098
23530
|
var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
23099
|
-
const root =
|
|
23100
|
-
const certificatePath =
|
|
23101
|
-
const privateKeyPath =
|
|
23102
|
-
const publicKeyPath =
|
|
23103
|
-
if (!
|
|
23531
|
+
const root = resolve13(options.projectRoot);
|
|
23532
|
+
const certificatePath = resolve13(root, options.certificatePath);
|
|
23533
|
+
const privateKeyPath = resolve13(root, options.privateKeyPath);
|
|
23534
|
+
const publicKeyPath = resolve13(root, options.publicKeyPath ?? resolve13(dirname13(privateKeyPath), "public-key.pem"));
|
|
23535
|
+
if (!inside4(root, certificatePath))
|
|
23104
23536
|
throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
|
|
23105
|
-
if (
|
|
23537
|
+
if (inside4(root, privateKeyPath) || inside4(root, publicKeyPath))
|
|
23106
23538
|
throw new TypeError("Expo code-signing private/public key output must remain outside the project and source control.");
|
|
23107
23539
|
const commonName = options.commonName.trim();
|
|
23108
23540
|
if (!commonName || commonName.length > 64)
|
|
@@ -23111,7 +23543,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
23111
23543
|
if (!Number.isSafeInteger(validityYears) || validityYears < 1 || validityYears > 20)
|
|
23112
23544
|
throw new TypeError("Expo code-signing validity must be an integer from 1 through 20 years.");
|
|
23113
23545
|
const collisions = (await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map(async (path) => ({
|
|
23114
|
-
exists: await
|
|
23546
|
+
exists: await exists6(path),
|
|
23115
23547
|
path
|
|
23116
23548
|
})))).filter((entry) => entry.exists);
|
|
23117
23549
|
if (collisions.length > 0)
|
|
@@ -23129,11 +23561,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
23129
23561
|
import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
|
|
23130
23562
|
const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
|
|
23131
23563
|
const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
|
|
23132
|
-
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) =>
|
|
23564
|
+
await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map((path) => mkdir15(dirname13(path), { recursive: true })));
|
|
23133
23565
|
await Promise.all([
|
|
23134
|
-
|
|
23135
|
-
|
|
23136
|
-
|
|
23566
|
+
writeFile16(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
|
|
23567
|
+
writeFile16(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
|
|
23568
|
+
writeFile16(publicKeyPath, publicKeyPEM, { flag: "wx", mode: 420 })
|
|
23137
23569
|
]);
|
|
23138
23570
|
return {
|
|
23139
23571
|
certificatePath,
|
|
@@ -23144,8 +23576,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
|
|
|
23144
23576
|
};
|
|
23145
23577
|
|
|
23146
23578
|
// src/mobile/updatePublisher.ts
|
|
23147
|
-
import { access as
|
|
23148
|
-
import { isAbsolute as
|
|
23579
|
+
import { access as access14 } from "fs/promises";
|
|
23580
|
+
import { isAbsolute as isAbsolute8, relative as relative13, resolve as resolve14, sep as sep9 } from "path";
|
|
23149
23581
|
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
23150
23582
|
var lifecycleMethod = (publisher, name) => {
|
|
23151
23583
|
const method = publisher[name];
|
|
@@ -23254,16 +23686,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
|
|
|
23254
23686
|
var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23255
23687
|
var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
|
|
23256
23688
|
var projectPath3 = (projectRoot, requested, label) => {
|
|
23257
|
-
const root =
|
|
23258
|
-
const path =
|
|
23259
|
-
const projectRelative =
|
|
23260
|
-
if (projectRelative === ".." || projectRelative.startsWith(`..${
|
|
23689
|
+
const root = resolve14(projectRoot);
|
|
23690
|
+
const path = resolve14(root, requested);
|
|
23691
|
+
const projectRelative = relative13(root, path);
|
|
23692
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep9}`) || isAbsolute8(projectRelative))
|
|
23261
23693
|
throw new TypeError(`${label} must remain inside the project.`);
|
|
23262
23694
|
return path;
|
|
23263
23695
|
};
|
|
23264
23696
|
var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
|
|
23265
23697
|
const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
|
|
23266
|
-
await
|
|
23698
|
+
await access14(modulePath).catch(() => {
|
|
23267
23699
|
throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
|
|
23268
23700
|
});
|
|
23269
23701
|
const loaded = await import(pathToFileURL3(modulePath).href);
|
|
@@ -23318,10 +23750,10 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
|
|
|
23318
23750
|
var NOT_FOUND4 = -1;
|
|
23319
23751
|
var ANDROID_EMULATOR_BOOT_POLL_MS = 500;
|
|
23320
23752
|
var ANDROID_EMULATOR_BOOT_TIMEOUT_MS = 180000;
|
|
23321
|
-
var
|
|
23753
|
+
var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23322
23754
|
var expoPublicConfig = (config) => {
|
|
23323
23755
|
const result = { ...config };
|
|
23324
|
-
if (!
|
|
23756
|
+
if (!isRecord8(result.updates))
|
|
23325
23757
|
return result;
|
|
23326
23758
|
const {
|
|
23327
23759
|
codeSigningCertificate: _codeSigningCertificate,
|
|
@@ -23361,13 +23793,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
|
|
|
23361
23793
|
];
|
|
23362
23794
|
var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
|
|
23363
23795
|
var directProjectPackages = async (projectRoot) => {
|
|
23364
|
-
const manifest = JSON.parse(await
|
|
23365
|
-
if (!
|
|
23796
|
+
const manifest = JSON.parse(await readFile20(join17(projectRoot, "package.json"), "utf8"));
|
|
23797
|
+
if (!isRecord8(manifest))
|
|
23366
23798
|
throw new TypeError("Application package.json must contain an object.");
|
|
23367
23799
|
const names = new Set;
|
|
23368
23800
|
for (const field2 of ["dependencies", "devDependencies"]) {
|
|
23369
23801
|
const dependencies = Reflect.get(manifest, field2);
|
|
23370
|
-
if (
|
|
23802
|
+
if (isRecord8(dependencies))
|
|
23371
23803
|
for (const name of Object.keys(dependencies))
|
|
23372
23804
|
names.add(name);
|
|
23373
23805
|
}
|
|
@@ -23375,8 +23807,8 @@ var directProjectPackages = async (projectRoot) => {
|
|
|
23375
23807
|
};
|
|
23376
23808
|
var resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
23377
23809
|
try {
|
|
23378
|
-
const manifest = JSON.parse(await
|
|
23379
|
-
return
|
|
23810
|
+
const manifest = JSON.parse(await readFile20(join17(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
23811
|
+
return isRecord8(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
23380
23812
|
} catch {
|
|
23381
23813
|
return;
|
|
23382
23814
|
}
|
|
@@ -23439,9 +23871,9 @@ var runCapacitor = async (projectRoot, args) => {
|
|
|
23439
23871
|
};
|
|
23440
23872
|
var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
|
|
23441
23873
|
var expoExecutable = async (project) => {
|
|
23442
|
-
const executable =
|
|
23874
|
+
const executable = join17(project, "node_modules", ".bin", "expo");
|
|
23443
23875
|
try {
|
|
23444
|
-
await
|
|
23876
|
+
await access15(executable);
|
|
23445
23877
|
return executable;
|
|
23446
23878
|
} catch {
|
|
23447
23879
|
throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
|
|
@@ -23473,14 +23905,14 @@ var runExpo = async (project, args, options = {}) => {
|
|
|
23473
23905
|
};
|
|
23474
23906
|
var ensureExpoPackages = async (project, args) => {
|
|
23475
23907
|
try {
|
|
23476
|
-
const manifest = JSON.parse(await
|
|
23908
|
+
const manifest = JSON.parse(await readFile20(join17(project, "package.json"), "utf8"));
|
|
23477
23909
|
const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
|
|
23478
23910
|
if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
|
|
23479
23911
|
throw new TypeError("Generated Expo dependencies are invalid.");
|
|
23480
23912
|
await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
|
|
23481
23913
|
if (typeof expected !== "string")
|
|
23482
23914
|
throw new TypeError("Generated Expo dependency version is invalid.");
|
|
23483
|
-
const installed = JSON.parse(await
|
|
23915
|
+
const installed = JSON.parse(await readFile20(join17(project, "node_modules", name, "package.json"), "utf8"));
|
|
23484
23916
|
const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
|
|
23485
23917
|
if (typeof actual !== "string")
|
|
23486
23918
|
throw new TypeError("Installed Expo dependency version is invalid.");
|
|
@@ -23678,7 +24110,7 @@ var sync = async (args) => {
|
|
|
23678
24110
|
};
|
|
23679
24111
|
var associations = async (args) => {
|
|
23680
24112
|
const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
|
|
23681
|
-
const outputDirectory =
|
|
24113
|
+
const outputDirectory = resolve15(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
|
|
23682
24114
|
if (args.includes("--verify")) {
|
|
23683
24115
|
const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
|
|
23684
24116
|
console.log(`Verified ${result2.results.length} hosted association files`);
|
|
@@ -23730,7 +24162,7 @@ var generateGithubCi = async (args) => {
|
|
|
23730
24162
|
const publicResult = {
|
|
23731
24163
|
changed: result.changed,
|
|
23732
24164
|
format: result.format,
|
|
23733
|
-
path:
|
|
24165
|
+
path: relative14(projectRoot, result.path).replaceAll("\\", "/"),
|
|
23734
24166
|
platforms: result.platforms,
|
|
23735
24167
|
publishing: result.publishing,
|
|
23736
24168
|
requiredSecrets: result.requiredSecrets
|
|
@@ -23841,9 +24273,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
23841
24273
|
temporaryDirectory: undefined
|
|
23842
24274
|
};
|
|
23843
24275
|
await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
|
|
23844
|
-
const temporaryParent =
|
|
23845
|
-
await
|
|
23846
|
-
const temporaryDirectory = await
|
|
24276
|
+
const temporaryParent = join17(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
|
|
24277
|
+
await mkdir16(temporaryParent, { recursive: true });
|
|
24278
|
+
const temporaryDirectory = await mkdtemp6(join17(temporaryParent, ".stage-"));
|
|
23847
24279
|
try {
|
|
23848
24280
|
await runExpo(mobile.nativeProjectDirectory, [
|
|
23849
24281
|
"export",
|
|
@@ -23855,9 +24287,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
23855
24287
|
temporaryDirectory,
|
|
23856
24288
|
"--clear"
|
|
23857
24289
|
], { production: true });
|
|
23858
|
-
const generatedApp = JSON.parse(await
|
|
23859
|
-
const expoConfig =
|
|
23860
|
-
if (!
|
|
24290
|
+
const generatedApp = JSON.parse(await readFile20(join17(mobile.nativeProjectDirectory, "app.json"), "utf8"));
|
|
24291
|
+
const expoConfig = isRecord8(generatedApp) ? generatedApp.expo : undefined;
|
|
24292
|
+
if (!isRecord8(expoConfig))
|
|
23861
24293
|
throw new TypeError("Generated Expo app configuration is invalid.");
|
|
23862
24294
|
const publicExpoConfig = expoPublicConfig(expoConfig);
|
|
23863
24295
|
const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
|
|
@@ -23873,7 +24305,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
|
|
|
23873
24305
|
temporaryDirectory
|
|
23874
24306
|
};
|
|
23875
24307
|
} catch (error) {
|
|
23876
|
-
await
|
|
24308
|
+
await rm9(temporaryDirectory, { force: true, recursive: true });
|
|
23877
24309
|
throw error;
|
|
23878
24310
|
}
|
|
23879
24311
|
};
|
|
@@ -23898,11 +24330,11 @@ var buildMobileUpdate = async (args) => {
|
|
|
23898
24330
|
let expoExportDirectory;
|
|
23899
24331
|
try {
|
|
23900
24332
|
await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
|
|
23901
|
-
const embedded = JSON.parse(await
|
|
23902
|
-
const runtimeFingerprint =
|
|
24333
|
+
const embedded = JSON.parse(await readFile20(join17(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
|
|
24334
|
+
const runtimeFingerprint = isRecord8(embedded) ? embedded.nativeRuntime : undefined;
|
|
23903
24335
|
if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
|
|
23904
24336
|
throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
|
|
23905
|
-
const privateKey = await
|
|
24337
|
+
const privateKey = await readFile20(resolve15(projectRoot, signingKeyPath));
|
|
23906
24338
|
const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
|
|
23907
24339
|
const derivedPublicKey = createPublicKey2(privateKey).export({
|
|
23908
24340
|
format: "der",
|
|
@@ -23923,7 +24355,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
23923
24355
|
channel: mobile.updates.channel,
|
|
23924
24356
|
classification,
|
|
23925
24357
|
keyId,
|
|
23926
|
-
outputDirectory: valueAfter(args, "--outdir") ??
|
|
24358
|
+
outputDirectory: valueAfter(args, "--outdir") ?? join17(projectRoot, ".absolutejs", "mobile", "updates"),
|
|
23927
24359
|
privateKey,
|
|
23928
24360
|
runtimeFingerprint
|
|
23929
24361
|
});
|
|
@@ -23935,7 +24367,7 @@ var buildMobileUpdate = async (args) => {
|
|
|
23935
24367
|
return result;
|
|
23936
24368
|
} finally {
|
|
23937
24369
|
if (expoExportDirectory)
|
|
23938
|
-
await
|
|
24370
|
+
await rm9(expoExportDirectory, { force: true, recursive: true });
|
|
23939
24371
|
sendTelemetryEvent("mobile:update-build", {
|
|
23940
24372
|
classification: classification ?? "invalid",
|
|
23941
24373
|
durationMs: Math.round(performance.now() - startedAt),
|
|
@@ -23965,7 +24397,7 @@ var generateExpoUpdateSigning = async (args) => {
|
|
|
23965
24397
|
...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
|
|
23966
24398
|
...validityYears === undefined ? {} : { validityYears }
|
|
23967
24399
|
});
|
|
23968
|
-
const certificatePath =
|
|
24400
|
+
const certificatePath = relative14(projectRoot, result.certificatePath).replaceAll("\\", "/");
|
|
23969
24401
|
sendTelemetryEvent("mobile:update-code-signing-generated", {
|
|
23970
24402
|
engine: "expo",
|
|
23971
24403
|
validityYears: validityYears ?? 10
|
|
@@ -24027,7 +24459,7 @@ var provisionMobileUpdate = async (args) => {
|
|
|
24027
24459
|
publicKeys: mobile.updates.publicKeys,
|
|
24028
24460
|
storage: requestedStorage
|
|
24029
24461
|
});
|
|
24030
|
-
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${
|
|
24462
|
+
console.log(`Provisioned ${requestedStorage === "local" ? "local development" : "durable S3-compatible"} mobile update storage in ${relative14(projectRoot, path).replaceAll("\\", "/")}.`);
|
|
24031
24463
|
if (requestedStorage === "local")
|
|
24032
24464
|
console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
|
|
24033
24465
|
else
|
|
@@ -24504,7 +24936,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
|
|
|
24504
24936
|
const durationMs = Math.round(performance.now() - startedAt);
|
|
24505
24937
|
console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
|
|
24506
24938
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
24507
|
-
console.log(`Metadata: ${
|
|
24939
|
+
console.log(`Metadata: ${join17(release.releaseRoot, "release.json")}`);
|
|
24508
24940
|
return release;
|
|
24509
24941
|
} finally {
|
|
24510
24942
|
sendTelemetryEvent("mobile:android-release-build", {
|
|
@@ -24623,8 +25055,6 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
24623
25055
|
const remoteProfile = requestedRemote !== undefined || process.platform !== "darwin" ? await getAbsoluteRemoteMacProfile(requestedRemote, remoteProfilePath()) : undefined;
|
|
24624
25056
|
if (process.platform !== "darwin" && !remoteProfile)
|
|
24625
25057
|
throw new TypeError("iOS release builds require macOS or a paired Remote Mac. Run `absolute mobile pair mac <name> <user@host>`.");
|
|
24626
|
-
if (remoteProfile && args.includes("--registered-device-artifact"))
|
|
24627
|
-
throw new TypeError("--registered-device-artifact currently requires a local Mac; paired Remote Mac transfer support is not yet available.");
|
|
24628
25058
|
let success = false;
|
|
24629
25059
|
const cancellation = remoteProfile ? new AbortController : undefined;
|
|
24630
25060
|
let stopListeningForCancellation = () => {
|
|
@@ -24652,6 +25082,7 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
24652
25082
|
},
|
|
24653
25083
|
...prepareBuildNumber === undefined ? {} : { prepareBuildNumber },
|
|
24654
25084
|
project: createAbsoluteRemoteIosDevProject(mobile, projectRoot, remoteProfile),
|
|
25085
|
+
registeredDeviceArtifact: args.includes("--registered-device-artifact"),
|
|
24655
25086
|
signal: cancellation?.signal
|
|
24656
25087
|
}) : await buildLocalIosRelease({
|
|
24657
25088
|
args,
|
|
@@ -24665,7 +25096,7 @@ var buildIos = async (args, prepareBuildNumber) => {
|
|
|
24665
25096
|
console.log(`Artifact: ${release.artifactPath}`);
|
|
24666
25097
|
if (release.registeredArtifactPath)
|
|
24667
25098
|
console.log(`Registered-device artifact: ${release.registeredArtifactPath}`);
|
|
24668
|
-
console.log(`Metadata: ${
|
|
25099
|
+
console.log(`Metadata: ${join17(release.releaseRoot, "release.json")}`);
|
|
24669
25100
|
return release;
|
|
24670
25101
|
} finally {
|
|
24671
25102
|
stopListeningForCancellation();
|
|
@@ -24777,7 +25208,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
24777
25208
|
checks.push({
|
|
24778
25209
|
id: "sync.storage-schema",
|
|
24779
25210
|
label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
|
|
24780
|
-
path:
|
|
25211
|
+
path: join17(projectRoot, "package.json"),
|
|
24781
25212
|
platform: "host",
|
|
24782
25213
|
status: "pass"
|
|
24783
25214
|
});
|
|
@@ -24785,7 +25216,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
|
|
|
24785
25216
|
checks.push({
|
|
24786
25217
|
id: "sync.storage-schema",
|
|
24787
25218
|
label: "Offline schema metadata is invalid",
|
|
24788
|
-
path:
|
|
25219
|
+
path: join17(projectRoot, "package.json"),
|
|
24789
25220
|
platform: "host",
|
|
24790
25221
|
remediation: error instanceof Error ? error.message : String(error),
|
|
24791
25222
|
status: "fail"
|
|
@@ -24873,7 +25304,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
|
|
|
24873
25304
|
}
|
|
24874
25305
|
return { https: args.includes("--https"), port };
|
|
24875
25306
|
}
|
|
24876
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
25307
|
+
const instances = listLiveInstances().filter((instance2) => resolve15(instance2.cwd) === resolve15(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
24877
25308
|
if (instances.length !== 1) {
|
|
24878
25309
|
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>.");
|
|
24879
25310
|
}
|
|
@@ -24967,8 +25398,8 @@ var selectOrStartAndroidReleaseEmulator = async (adb2, emulator, explicitSerial,
|
|
|
24967
25398
|
return waitForBoot();
|
|
24968
25399
|
};
|
|
24969
25400
|
var safeArtifactRoot = (projectRoot, value) => {
|
|
24970
|
-
const root =
|
|
24971
|
-
if (root !== projectRoot && !root.startsWith(`${
|
|
25401
|
+
const root = resolve15(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
|
|
25402
|
+
if (root !== projectRoot && !root.startsWith(`${resolve15(projectRoot)}/`)) {
|
|
24972
25403
|
throw new TypeError("mobile test --artifacts must remain inside the project.");
|
|
24973
25404
|
}
|
|
24974
25405
|
return root;
|
|
@@ -24996,12 +25427,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
|
|
|
24996
25427
|
});
|
|
24997
25428
|
};
|
|
24998
25429
|
var writeAndroidFailureArtifacts = async (options) => {
|
|
24999
|
-
await
|
|
25000
|
-
const screenshot = options.session ? await options.session.screenshot(
|
|
25430
|
+
await mkdir16(options.artifactRoot, { recursive: true });
|
|
25431
|
+
const screenshot = options.session ? await options.session.screenshot(join17(options.artifactRoot, "android-failure.png")).catch(() => {
|
|
25001
25432
|
return;
|
|
25002
25433
|
}) : undefined;
|
|
25003
|
-
const diagnosticPath =
|
|
25004
|
-
await
|
|
25434
|
+
const diagnosticPath = join17(options.artifactRoot, "android-failure.json");
|
|
25435
|
+
await writeFile17(diagnosticPath, `${JSON.stringify({
|
|
25005
25436
|
diagnostics: options.session?.diagnostics ?? [],
|
|
25006
25437
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
25007
25438
|
platform: "android",
|
|
@@ -25022,7 +25453,7 @@ var captureAndroidReleaseScreenshot = async (adb2, serial, destination) => {
|
|
|
25022
25453
|
]);
|
|
25023
25454
|
if (exitCode !== 0)
|
|
25024
25455
|
throw new Error(`Android release screenshot failed: ${stderr.trim() || `status ${exitCode}`}`);
|
|
25025
|
-
await
|
|
25456
|
+
await writeFile17(destination, bytes);
|
|
25026
25457
|
return destination;
|
|
25027
25458
|
};
|
|
25028
25459
|
var printAndroidReleaseAcceptance = (result, json) => {
|
|
@@ -25076,7 +25507,7 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
25076
25507
|
release,
|
|
25077
25508
|
serial
|
|
25078
25509
|
});
|
|
25079
|
-
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial,
|
|
25510
|
+
const screenshot = reportRoot ? await captureAndroidReleaseScreenshot(adb2, serial, join17(reportRoot, "android-release.png")) : undefined;
|
|
25080
25511
|
sendTelemetryEvent("mobile:android-release-conformance", {
|
|
25081
25512
|
durationMs: result.durationMs,
|
|
25082
25513
|
engine: result.engine,
|
|
@@ -25123,9 +25554,9 @@ var testAndroidRelease = async (args, mobile, projectRoot) => {
|
|
|
25123
25554
|
platform: "android",
|
|
25124
25555
|
success: false
|
|
25125
25556
|
});
|
|
25126
|
-
await
|
|
25127
|
-
const diagnosticPath =
|
|
25128
|
-
await
|
|
25557
|
+
await mkdir16(artifactRoot, { recursive: true });
|
|
25558
|
+
const diagnosticPath = join17(artifactRoot, "android-release-failure.json");
|
|
25559
|
+
await writeFile17(diagnosticPath, `${JSON.stringify({
|
|
25129
25560
|
error: sanitizeNativeReportText(error instanceof Error ? error.message : String(error)),
|
|
25130
25561
|
platform: "android",
|
|
25131
25562
|
provider: mobile.engine,
|
|
@@ -25219,7 +25650,7 @@ var testAndroid = async (args) => {
|
|
|
25219
25650
|
console.log(JSON.stringify(report, null, 2));
|
|
25220
25651
|
else
|
|
25221
25652
|
printAndroidTestReport(report);
|
|
25222
|
-
const screenshot = reportRoot ? await session.screenshot(
|
|
25653
|
+
const screenshot = reportRoot ? await session.screenshot(join17(artifactRoot, "android-emulator.png")) : undefined;
|
|
25223
25654
|
await writeRequestedAndroidReport({
|
|
25224
25655
|
adb: adb2,
|
|
25225
25656
|
args,
|
|
@@ -25288,14 +25719,14 @@ var requireIosTestContext = (args, projectRoot) => {
|
|
|
25288
25719
|
const port = Number(explicit);
|
|
25289
25720
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
25290
25721
|
throw new TypeError("mobile test --port must be a valid TCP port.");
|
|
25291
|
-
const instance2 = listLiveInstances().find((candidate) =>
|
|
25722
|
+
const instance2 = listLiveInstances().find((candidate) => resolve15(candidate.cwd) === resolve15(projectRoot) && candidate.source === "dev" && candidate.port === port);
|
|
25292
25723
|
return {
|
|
25293
25724
|
https: instance2?.https ?? args.includes("--https"),
|
|
25294
25725
|
instance: instance2,
|
|
25295
25726
|
port
|
|
25296
25727
|
};
|
|
25297
25728
|
}
|
|
25298
|
-
const instances = listLiveInstances().filter((instance2) =>
|
|
25729
|
+
const instances = listLiveInstances().filter((instance2) => resolve15(instance2.cwd) === resolve15(projectRoot) && instance2.source === "dev" && instance2.port !== null);
|
|
25299
25730
|
if (instances.length !== 1)
|
|
25300
25731
|
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>.");
|
|
25301
25732
|
const [instance] = instances;
|
|
@@ -25313,7 +25744,7 @@ var waitForIosHmrClient = async (options) => {
|
|
|
25313
25744
|
return;
|
|
25314
25745
|
});
|
|
25315
25746
|
const status = response?.ok ? await response.json().catch(() => null) : null;
|
|
25316
|
-
const targets =
|
|
25747
|
+
const targets = isRecord8(status) && isRecord8(status.connectedTargets) ? status.connectedTargets : undefined;
|
|
25317
25748
|
if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
|
|
25318
25749
|
return;
|
|
25319
25750
|
await Bun.sleep(100);
|
|
@@ -25439,8 +25870,8 @@ var requireCapturedCommand = (command, label) => {
|
|
|
25439
25870
|
return result;
|
|
25440
25871
|
};
|
|
25441
25872
|
var writeIosFailureArtifacts = async (options) => {
|
|
25442
|
-
await
|
|
25443
|
-
const screenshot =
|
|
25873
|
+
await mkdir16(options.artifactRoot, { recursive: true });
|
|
25874
|
+
const screenshot = join17(options.artifactRoot, "ios-failure.png");
|
|
25444
25875
|
const screenshotResult = captureCommand4([
|
|
25445
25876
|
options.xcrun,
|
|
25446
25877
|
"simctl",
|
|
@@ -25449,8 +25880,8 @@ var writeIosFailureArtifacts = async (options) => {
|
|
|
25449
25880
|
"screenshot",
|
|
25450
25881
|
screenshot
|
|
25451
25882
|
]);
|
|
25452
|
-
const diagnosticPath =
|
|
25453
|
-
await
|
|
25883
|
+
const diagnosticPath = join17(options.artifactRoot, "ios-failure.json");
|
|
25884
|
+
await writeFile17(diagnosticPath, `${JSON.stringify({
|
|
25454
25885
|
appId: options.appId,
|
|
25455
25886
|
error: options.error instanceof Error ? options.error.message : String(options.error),
|
|
25456
25887
|
platform: "ios",
|
|
@@ -25477,8 +25908,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
|
|
|
25477
25908
|
var absolutejsVersionForReport = async () => {
|
|
25478
25909
|
let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
|
|
25479
25910
|
const versions = await Promise.all([
|
|
25480
|
-
|
|
25481
|
-
|
|
25911
|
+
resolve15(import.meta.dir, "..", "..", "package.json"),
|
|
25912
|
+
resolve15(import.meta.dir, "..", "..", "..", "package.json")
|
|
25482
25913
|
].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
|
|
25483
25914
|
for (const version of versions) {
|
|
25484
25915
|
if (version === "unknown")
|
|
@@ -25655,7 +26086,7 @@ var printIosReleaseAcceptance = (result, json) => {
|
|
|
25655
26086
|
console.log(`\u2713 Embedded local content launched in ${getDurationString(result.launchMs)} and relaunched in ${getDurationString(result.relaunchMs)}.`);
|
|
25656
26087
|
console.log(result.networkUnavailable === "user-confirmed" ? "\u2713 Physical-device network-unavailable state was explicitly confirmed." : "\u2139 Simulator evidence does not claim physical-device offline behavior.");
|
|
25657
26088
|
};
|
|
25658
|
-
var iosReleaseRun = (result, appId, targetId) => ({
|
|
26089
|
+
var iosReleaseRun = (result, appId, targetId, remote) => ({
|
|
25659
26090
|
appId,
|
|
25660
26091
|
durationMs: result.durationMs,
|
|
25661
26092
|
hmrConnected: false,
|
|
@@ -25671,6 +26102,7 @@ var iosReleaseRun = (result, appId, targetId) => ({
|
|
|
25671
26102
|
networkUnavailable: result.networkUnavailable,
|
|
25672
26103
|
relaunchMs: result.relaunchMs,
|
|
25673
26104
|
releaseId: result.releaseId,
|
|
26105
|
+
remote,
|
|
25674
26106
|
signed: result.signed
|
|
25675
26107
|
},
|
|
25676
26108
|
status: "pass",
|
|
@@ -25704,14 +26136,56 @@ var runIosSimulatorReleaseTarget = async (options) => {
|
|
|
25704
26136
|
});
|
|
25705
26137
|
return { result, targetId: simulator.udid };
|
|
25706
26138
|
};
|
|
26139
|
+
var runRemoteIosReleaseTarget = async (options) => {
|
|
26140
|
+
const physical = options.requestedDevice !== undefined;
|
|
26141
|
+
const confirmed = !physical || options.args.includes("--yes") || await confirmInstall("On the selected iPhone, enable Airplane Mode and then disable Wi-Fi in Settings. Confirm both are still disabled and continue with two release launches on the Remote Mac?");
|
|
26142
|
+
if (!confirmed)
|
|
26143
|
+
throw new TypeError("Physical iOS offline acceptance was cancelled because network-unavailable state was not confirmed.");
|
|
26144
|
+
let distribution = "simulator-release";
|
|
26145
|
+
if (options.requestedDevice)
|
|
26146
|
+
distribution = "registered-device";
|
|
26147
|
+
if (options.args.includes("--testflight"))
|
|
26148
|
+
distribution = "testflight";
|
|
26149
|
+
const simulatorUdid = valueAfter(options.args, "--udid") ?? valueAfter(options.args, "--serial");
|
|
26150
|
+
const remoteAcceptance = await runAbsoluteRemoteIosReleaseAcceptance({
|
|
26151
|
+
...options.requestedDevice ? {
|
|
26152
|
+
deviceIdentifier: normalizeAbsoluteIosDeviceIdentifier(options.requestedDevice),
|
|
26153
|
+
networkUnavailableConfirmed: true
|
|
26154
|
+
} : {},
|
|
26155
|
+
distribution,
|
|
26156
|
+
project: createAbsoluteRemoteIosDevProject(options.mobile, options.projectRoot, options.profile),
|
|
26157
|
+
release: options.release,
|
|
26158
|
+
signal: options.cancellation?.signal,
|
|
26159
|
+
log: (message) => console.log(`[remote] ${message}`),
|
|
26160
|
+
onPhaseTiming: ({ durationMs, phase }) => {
|
|
26161
|
+
console.log(`[mobile:ios-release-test] ${phase} ${getDurationString(durationMs)}`);
|
|
26162
|
+
sendTelemetryEvent("mobile:ios-release-test-phase", {
|
|
26163
|
+
durationMs: Math.round(durationMs),
|
|
26164
|
+
engine: options.mobile.engine,
|
|
26165
|
+
phase,
|
|
26166
|
+
platform: "ios",
|
|
26167
|
+
provider: "remote-mac"
|
|
26168
|
+
});
|
|
26169
|
+
},
|
|
26170
|
+
...simulatorUdid ? { simulatorUdid } : {}
|
|
26171
|
+
});
|
|
26172
|
+
return {
|
|
26173
|
+
result: remoteAcceptance.result,
|
|
26174
|
+
targetId: remoteAcceptance.targetId
|
|
26175
|
+
};
|
|
26176
|
+
};
|
|
25707
26177
|
var testIosRelease = async (args, mobile, projectRoot) => {
|
|
25708
26178
|
const requested = valueAfter(args, "--release");
|
|
25709
26179
|
if (!requested || requested.startsWith("--"))
|
|
25710
26180
|
throw new TypeError("mobile test ios --release requires a release directory or release.json path.");
|
|
25711
26181
|
if (!mobile.platforms.includes("ios"))
|
|
25712
26182
|
throw new TypeError("mobile test ios requires ios in mobile.platforms.");
|
|
25713
|
-
|
|
25714
|
-
|
|
26183
|
+
const requestedRemote = valueAfter(args, "--remote");
|
|
26184
|
+
if (args.includes("--remote") && (!requestedRemote || requestedRemote.startsWith("-")))
|
|
26185
|
+
throw new TypeError("mobile test ios --release requires --remote <name>.");
|
|
26186
|
+
const remoteProfile = requestedRemote !== undefined || process.platform !== "darwin" ? await getAbsoluteRemoteMacProfile(requestedRemote, remoteProfilePath()) : undefined;
|
|
26187
|
+
if (process.platform !== "darwin" && !remoteProfile)
|
|
26188
|
+
throw new TypeError("iOS release acceptance requires macOS or a paired Remote Mac. Run `absolute mobile pair mac <name> <user@host>`.");
|
|
25715
26189
|
const release = await readAbsoluteIosRelease(projectRoot, requested);
|
|
25716
26190
|
if (release.metadata.appId !== mobile.appId)
|
|
25717
26191
|
throw new TypeError(`iOS release app ID ${release.metadata.appId} does not match mobile.appId ${mobile.appId}.`);
|
|
@@ -25726,24 +26200,59 @@ var testIosRelease = async (args, mobile, projectRoot) => {
|
|
|
25726
26200
|
throw new TypeError("mobile test ios --release --testflight requires --device.");
|
|
25727
26201
|
if (requestedDevice && (valueAfter(args, "--udid") || valueAfter(args, "--serial")))
|
|
25728
26202
|
throw new TypeError("mobile test ios --release --device cannot be combined with a simulator selector.");
|
|
25729
|
-
const xcrun = await requireIosXcrun();
|
|
26203
|
+
const xcrun = remoteProfile ? undefined : await requireIosXcrun();
|
|
25730
26204
|
const reportRoot = nativeReportRoot(args, projectRoot, "ios");
|
|
25731
26205
|
const artifactRoot = reportRoot ?? safeArtifactRoot(projectRoot, valueAfter(args, "--artifacts") ?? `.absolutejs/mobile/test-artifacts/${release.metadata.releaseId}`);
|
|
25732
26206
|
const startedAt = performance.now();
|
|
25733
26207
|
let targetId = requestedDevice ?? "ios-simulator";
|
|
26208
|
+
let remoteReportMetadata;
|
|
26209
|
+
if (remoteProfile && reportRoot) {
|
|
26210
|
+
const macos = await captureAbsoluteRemoteMacCommand(remoteProfile, [
|
|
26211
|
+
"/usr/bin/sw_vers",
|
|
26212
|
+
"-productVersion"
|
|
26213
|
+
]);
|
|
26214
|
+
if (macos.exitCode !== 0)
|
|
26215
|
+
throw new Error("Remote Mac version inspection failed.");
|
|
26216
|
+
remoteReportMetadata = {
|
|
26217
|
+
absolutejsVersion: await absolutejsVersionForReport(),
|
|
26218
|
+
bunVersion: Bun.version,
|
|
26219
|
+
macosVersion: macos.stdout.trim(),
|
|
26220
|
+
xcodeVersion: remoteProfile.xcodeVersion
|
|
26221
|
+
};
|
|
26222
|
+
}
|
|
26223
|
+
const cancellation = remoteProfile ? new AbortController : undefined;
|
|
26224
|
+
const stopListeningForCancellation = remoteProfile ? listenForRemoteReleaseCancellation(cancellation) : () => {
|
|
26225
|
+
return;
|
|
26226
|
+
};
|
|
25734
26227
|
try {
|
|
25735
|
-
|
|
25736
|
-
|
|
25737
|
-
|
|
25738
|
-
|
|
25739
|
-
|
|
25740
|
-
|
|
25741
|
-
|
|
25742
|
-
|
|
25743
|
-
|
|
25744
|
-
|
|
25745
|
-
|
|
25746
|
-
|
|
26228
|
+
let acceptance;
|
|
26229
|
+
if (remoteProfile)
|
|
26230
|
+
acceptance = await runRemoteIosReleaseTarget({
|
|
26231
|
+
args,
|
|
26232
|
+
...cancellation ? { cancellation } : {},
|
|
26233
|
+
mobile,
|
|
26234
|
+
profile: remoteProfile,
|
|
26235
|
+
projectRoot,
|
|
26236
|
+
release,
|
|
26237
|
+
...requestedDevice ? { requestedDevice } : {}
|
|
26238
|
+
});
|
|
26239
|
+
else if (!xcrun)
|
|
26240
|
+
throw new Error("Local iOS release acceptance requires xcrun.");
|
|
26241
|
+
else if (requestedDevice)
|
|
26242
|
+
acceptance = await runIosPhysicalReleaseTarget({
|
|
26243
|
+
args,
|
|
26244
|
+
device: requestedDevice,
|
|
26245
|
+
release,
|
|
26246
|
+
xcrun
|
|
26247
|
+
});
|
|
26248
|
+
else
|
|
26249
|
+
acceptance = await runIosSimulatorReleaseTarget({
|
|
26250
|
+
args,
|
|
26251
|
+
artifactRoot,
|
|
26252
|
+
mobile,
|
|
26253
|
+
release,
|
|
26254
|
+
xcrun
|
|
26255
|
+
});
|
|
25747
26256
|
const { result, targetId: acceptedTargetId } = acceptance;
|
|
25748
26257
|
targetId = acceptedTargetId;
|
|
25749
26258
|
sendTelemetryEvent("mobile:ios-release-conformance", {
|
|
@@ -25755,14 +26264,16 @@ var testIosRelease = async (args, mobile, projectRoot) => {
|
|
|
25755
26264
|
launchMs: result.launchMs,
|
|
25756
26265
|
platform: "ios",
|
|
25757
26266
|
relaunchMs: result.relaunchMs,
|
|
26267
|
+
remote: remoteProfile !== undefined,
|
|
25758
26268
|
success: true
|
|
25759
26269
|
});
|
|
25760
26270
|
await writeRequestedIosReport({
|
|
25761
26271
|
args,
|
|
26272
|
+
...remoteReportMetadata ? { metadata: remoteReportMetadata } : {},
|
|
25762
26273
|
projectRoot,
|
|
25763
26274
|
provider: mobile.engine,
|
|
25764
|
-
run: iosReleaseRun(result, mobile.appId, targetId),
|
|
25765
|
-
xcrun
|
|
26275
|
+
run: iosReleaseRun(result, mobile.appId, targetId, remoteProfile !== undefined),
|
|
26276
|
+
...xcrun ? { xcrun } : {}
|
|
25766
26277
|
});
|
|
25767
26278
|
printIosReleaseAcceptance(result, args.includes("--json"));
|
|
25768
26279
|
return result;
|
|
@@ -25772,11 +26283,12 @@ var testIosRelease = async (args, mobile, projectRoot) => {
|
|
|
25772
26283
|
durationMs,
|
|
25773
26284
|
engine: mobile.engine,
|
|
25774
26285
|
platform: "ios",
|
|
26286
|
+
remote: remoteProfile !== undefined,
|
|
25775
26287
|
success: false
|
|
25776
26288
|
});
|
|
25777
|
-
await
|
|
25778
|
-
const diagnosticPath =
|
|
25779
|
-
await
|
|
26289
|
+
await mkdir16(artifactRoot, { recursive: true });
|
|
26290
|
+
const diagnosticPath = join17(artifactRoot, "ios-release-failure.json");
|
|
26291
|
+
await writeFile17(diagnosticPath, `${JSON.stringify({
|
|
25780
26292
|
error: sanitizeIosReportText(error instanceof Error ? error.message : String(error)),
|
|
25781
26293
|
platform: "ios",
|
|
25782
26294
|
provider: mobile.engine,
|
|
@@ -25787,6 +26299,7 @@ var testIosRelease = async (args, mobile, projectRoot) => {
|
|
|
25787
26299
|
if (reportRoot)
|
|
25788
26300
|
await writeRequestedIosReport({
|
|
25789
26301
|
args,
|
|
26302
|
+
...remoteReportMetadata ? { metadata: remoteReportMetadata } : {},
|
|
25790
26303
|
projectRoot,
|
|
25791
26304
|
provider: mobile.engine,
|
|
25792
26305
|
run: {
|
|
@@ -25798,9 +26311,11 @@ var testIosRelease = async (args, mobile, projectRoot) => {
|
|
|
25798
26311
|
targetId,
|
|
25799
26312
|
targetKind: requestedDevice ? "device" : "simulator"
|
|
25800
26313
|
},
|
|
25801
|
-
xcrun
|
|
26314
|
+
...xcrun ? { xcrun } : {}
|
|
25802
26315
|
});
|
|
25803
26316
|
throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}`, { cause: error });
|
|
26317
|
+
} finally {
|
|
26318
|
+
stopListeningForCancellation();
|
|
25804
26319
|
}
|
|
25805
26320
|
};
|
|
25806
26321
|
var testIos = async (args) => {
|
|
@@ -25866,8 +26381,8 @@ var testIos = async (args) => {
|
|
|
25866
26381
|
mobile.appId
|
|
25867
26382
|
], "iOS app launch");
|
|
25868
26383
|
await waitForIosHmrClient({ https, port, timeoutMs });
|
|
25869
|
-
await
|
|
25870
|
-
const screenshot =
|
|
26384
|
+
await mkdir16(artifactRoot, { recursive: true });
|
|
26385
|
+
const screenshot = join17(artifactRoot, "ios-simulator.png");
|
|
25871
26386
|
requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
|
|
25872
26387
|
const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
|
|
25873
26388
|
const report = {
|
|
@@ -25951,6 +26466,110 @@ var testIos = async (args) => {
|
|
|
25951
26466
|
throw new Error(`${error instanceof Error ? error.message : String(error)} Failure diagnostics: ${diagnosticPath}${screenshot ? `; screenshot: ${screenshot}` : ""}`, { cause: error });
|
|
25952
26467
|
}
|
|
25953
26468
|
};
|
|
26469
|
+
var readCertifiableMobileRelease = async (projectRoot, requested) => {
|
|
26470
|
+
let androidError;
|
|
26471
|
+
try {
|
|
26472
|
+
return await readAbsoluteAndroidRelease(projectRoot, requested);
|
|
26473
|
+
} catch (error) {
|
|
26474
|
+
androidError = error;
|
|
26475
|
+
}
|
|
26476
|
+
try {
|
|
26477
|
+
return await readAbsoluteIosRelease(projectRoot, requested);
|
|
26478
|
+
} catch (iosError) {
|
|
26479
|
+
throw new TypeError(`Unable to read an immutable Android or iOS release from ${requested}. Android: ${androidError instanceof Error ? androidError.message : String(androidError)} iOS: ${iosError instanceof Error ? iosError.message : String(iosError)}`, { cause: iosError });
|
|
26480
|
+
}
|
|
26481
|
+
};
|
|
26482
|
+
var mobileCertificationRequirement = (value) => {
|
|
26483
|
+
if (value === undefined)
|
|
26484
|
+
return;
|
|
26485
|
+
if (value !== "installed" && value !== "simulator" && value !== "device" && value !== "store")
|
|
26486
|
+
throw new TypeError("mobile certify --require must be installed, simulator, device, or store.");
|
|
26487
|
+
return value;
|
|
26488
|
+
};
|
|
26489
|
+
var mobileCertificationValue = (args, flag) => {
|
|
26490
|
+
const value = valueAfter(args, flag);
|
|
26491
|
+
if (args.includes(flag) && (!value || value.startsWith("--")))
|
|
26492
|
+
throw new TypeError(`mobile certify ${flag} requires a value.`);
|
|
26493
|
+
return value;
|
|
26494
|
+
};
|
|
26495
|
+
var verifyMobileCertification = async (options) => {
|
|
26496
|
+
if (valuesAfter(options.args, "--evidence").length > 0)
|
|
26497
|
+
throw new TypeError("mobile certify --verify cannot be combined with --evidence.");
|
|
26498
|
+
const loaded = await readAbsoluteMobileReleaseCertification(options.projectRoot, options.verifyPath);
|
|
26499
|
+
const certification = verifyAbsoluteMobileReleaseCertification(loaded.certification, options.release, options.requirement);
|
|
26500
|
+
sendTelemetryEvent("mobile:release-certification", {
|
|
26501
|
+
durationMs: Math.round(performance.now() - options.startedAt),
|
|
26502
|
+
engine: options.release.metadata.engine,
|
|
26503
|
+
mode: "verify",
|
|
26504
|
+
platform: options.release.metadata.platform,
|
|
26505
|
+
requirement: options.requirement ?? certification.requirement,
|
|
26506
|
+
strength: certification.strength,
|
|
26507
|
+
success: true
|
|
26508
|
+
});
|
|
26509
|
+
if (options.args.includes("--json"))
|
|
26510
|
+
console.log(JSON.stringify(certification, null, 2));
|
|
26511
|
+
else
|
|
26512
|
+
console.log(`\u2713 ${certification.strength} certification ${certification.certificationId} still matches ${certification.release.releaseId}.`);
|
|
26513
|
+
return certification;
|
|
26514
|
+
};
|
|
26515
|
+
var createMobileCertification = async (options) => {
|
|
26516
|
+
const evidencePaths = valuesAfter(options.args, "--evidence");
|
|
26517
|
+
const certification = await createAbsoluteMobileReleaseCertification({
|
|
26518
|
+
evidencePaths,
|
|
26519
|
+
projectRoot: options.projectRoot,
|
|
26520
|
+
release: options.release,
|
|
26521
|
+
...options.requirement ? { requirement: options.requirement } : {}
|
|
26522
|
+
});
|
|
26523
|
+
const output = await writeAbsoluteMobileReleaseCertification(options.projectRoot, certification, mobileCertificationValue(options.args, "--outdir"));
|
|
26524
|
+
sendTelemetryEvent("mobile:release-certification", {
|
|
26525
|
+
durationMs: Math.round(performance.now() - options.startedAt),
|
|
26526
|
+
engine: options.release.metadata.engine,
|
|
26527
|
+
evidenceCount: certification.evidence.length,
|
|
26528
|
+
mode: "create",
|
|
26529
|
+
platform: options.release.metadata.platform,
|
|
26530
|
+
requirement: certification.requirement,
|
|
26531
|
+
strength: certification.strength,
|
|
26532
|
+
success: true
|
|
26533
|
+
});
|
|
26534
|
+
if (options.args.includes("--json"))
|
|
26535
|
+
console.log(JSON.stringify(certification, null, 2));
|
|
26536
|
+
else {
|
|
26537
|
+
console.log(`\u2713 Certified immutable ${certification.release.platform} release ${certification.release.releaseId} at ${certification.strength} strength.`);
|
|
26538
|
+
console.log(`Certification: ${output.jsonPath}`);
|
|
26539
|
+
console.log(`Summary: ${output.markdownPath}`);
|
|
26540
|
+
}
|
|
26541
|
+
return certification;
|
|
26542
|
+
};
|
|
26543
|
+
var certifyMobileRelease = async (args) => {
|
|
26544
|
+
const [requested] = args;
|
|
26545
|
+
if (!requested || requested.startsWith("--"))
|
|
26546
|
+
throw new TypeError("mobile certify requires a release directory or release.json path.");
|
|
26547
|
+
const projectRoot = process.cwd();
|
|
26548
|
+
const release = await readCertifiableMobileRelease(projectRoot, requested);
|
|
26549
|
+
const requirement = mobileCertificationRequirement(mobileCertificationValue(args, "--require"));
|
|
26550
|
+
const verifyPath = mobileCertificationValue(args, "--verify");
|
|
26551
|
+
const startedAt = performance.now();
|
|
26552
|
+
try {
|
|
26553
|
+
const options = {
|
|
26554
|
+
args,
|
|
26555
|
+
projectRoot,
|
|
26556
|
+
release,
|
|
26557
|
+
...requirement ? { requirement } : {},
|
|
26558
|
+
startedAt
|
|
26559
|
+
};
|
|
26560
|
+
return verifyPath ? await verifyMobileCertification({ ...options, verifyPath }) : await createMobileCertification(options);
|
|
26561
|
+
} catch (error) {
|
|
26562
|
+
sendTelemetryEvent("mobile:release-certification", {
|
|
26563
|
+
durationMs: Math.round(performance.now() - startedAt),
|
|
26564
|
+
engine: release.metadata.engine,
|
|
26565
|
+
mode: verifyPath ? "verify" : "create",
|
|
26566
|
+
platform: release.metadata.platform,
|
|
26567
|
+
...requirement ? { requirement } : {},
|
|
26568
|
+
success: false
|
|
26569
|
+
});
|
|
26570
|
+
throw error;
|
|
26571
|
+
}
|
|
26572
|
+
};
|
|
25954
26573
|
var runMobile = async (args) => {
|
|
25955
26574
|
const [command] = args;
|
|
25956
26575
|
if (command === "pair") {
|
|
@@ -25989,6 +26608,10 @@ var runMobile = async (args) => {
|
|
|
25989
26608
|
await inspectMobile(args.slice(1));
|
|
25990
26609
|
return;
|
|
25991
26610
|
}
|
|
26611
|
+
if (command === "certify") {
|
|
26612
|
+
await certifyMobileRelease(args.slice(1));
|
|
26613
|
+
return;
|
|
26614
|
+
}
|
|
25992
26615
|
if (command === "test" && args[1] === "android") {
|
|
25993
26616
|
await testAndroid(args.slice(2));
|
|
25994
26617
|
return;
|
|
@@ -26053,7 +26676,7 @@ var runMobile = async (args) => {
|
|
|
26053
26676
|
await publishIos(args.slice(2));
|
|
26054
26677
|
return;
|
|
26055
26678
|
}
|
|
26056
|
-
throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
|
|
26679
|
+
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] | certify <release-dir> [--evidence report-dir]... [--require installed|simulator|device|store] [--outdir dir] [--json] | certify <release-dir> --verify certification-dir [--require installed|simulator|device|store] [--json] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--registered-device-artifact] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--release release-dir [--yes] | --route path [--wait-for-hmr] [--port n]] [--report [dir]] [--serial id] [--artifacts dir] [--json] [--config path] | test ios [--release release-dir [--remote name] [--device id [--testflight] --yes] | --wait-for-hmr] [--report [dir]] [--udid id] [--artifacts dir] [--json] [--config path]");
|
|
26057
26680
|
};
|
|
26058
26681
|
export {
|
|
26059
26682
|
runMobile
|