@absolutejs/absolute 0.20.0-beta.84 → 0.20.0-beta.86
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/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/build.js +370 -17
- package/dist/build.js.map +5 -5
- package/dist/cli/{compile-t73ac1zb.js → compile-e9cn3xpx.js} +4 -4
- package/dist/cli/{config-naw5fer4.js → config-z2kf085h.js} +1 -1
- package/dist/cli/{dev-skb8dz33.js → dev-3hweza3s.js} +2 -2
- package/dist/cli/{expoProject-3mmwtemn.js → expoProject-dac8fqhq.js} +1 -1
- package/dist/cli/{index-czw3jd8f.js → index-3tsmbyze.js} +22 -1
- package/dist/cli/{index-q78x1k9q.js → index-9rk7v35t.js} +2 -2
- package/dist/cli/{index-p8ezgd5e.js → index-tame6e2c.js} +65 -3
- package/dist/cli/{index-8gksmws3.js → index-x48brywr.js} +2 -2
- package/dist/cli/index.js +5 -5
- package/dist/cli/{mobile-pdck6a35.js → mobile-2p3z33t1.js} +85 -8
- package/dist/cli/{start-jbgfw27x.js → start-7t186mhg.js} +4 -4
- package/dist/index.js +370 -17
- package/dist/index.js.map +5 -5
- package/dist/mobile/index.js +689 -69
- package/dist/mobile/index.js.map +8 -8
- package/dist/mobile/remoteMacAgentEntry.js +237 -175
- package/dist/mobile/shellUpdate.js +415 -67
- package/dist/src/mobile/config.d.ts +5 -0
- package/dist/src/mobile/updateClient.d.ts +33 -0
- package/dist/src/mobile/updatePublisher.d.ts +12 -1
- package/dist/src/mobile/updateServer.d.ts +10 -0
- package/dist/types/build.d.ts +9 -0
- package/package.json +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __require = import.meta.require;
|
|
3
3
|
|
|
4
|
-
// .angular-partial-tmp-
|
|
4
|
+
// .angular-partial-tmp-sMuxmM/src/core/streamingSlotRegistrar.ts
|
|
5
5
|
var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
|
|
6
6
|
var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
|
|
7
7
|
var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __require = import.meta.require;
|
|
3
3
|
|
|
4
|
-
// .angular-partial-tmp-
|
|
4
|
+
// .angular-partial-tmp-sMuxmM/src/core/streamingSlotRegistrar.ts
|
|
5
5
|
var STREAMING_SLOT_REGISTRAR_KEY = Symbol.for("absolutejs.streamingSlotRegistrar");
|
|
6
6
|
var STREAMING_SLOT_WARNING_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotWarningController");
|
|
7
7
|
var STREAMING_SLOT_COLLECTION_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotCollectionController");
|
|
@@ -48,7 +48,7 @@ var warnMissingStreamingSlotCollector = (primitiveName) => {
|
|
|
48
48
|
getWarningController()?.maybeWarn(primitiveName);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
// .angular-partial-tmp-
|
|
51
|
+
// .angular-partial-tmp-sMuxmM/src/core/streamingSlotRegistry.ts
|
|
52
52
|
var STREAMING_SLOT_STORAGE_KEY = Symbol.for("absolutejs.streamingSlotAsyncLocalStorage");
|
|
53
53
|
var isObjectRecord2 = (value) => Boolean(value) && typeof value === "object";
|
|
54
54
|
var isAsyncLocalStorage = (value) => isObjectRecord2(value) && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
|
package/dist/build.js
CHANGED
|
@@ -27625,7 +27625,7 @@ __export(exports_config, {
|
|
|
27625
27625
|
import { readFileSync as readFileSync35 } from "fs";
|
|
27626
27626
|
import { resolve as resolve44 } from "path";
|
|
27627
27627
|
import { createHash as createHash5, createPublicKey, X509Certificate } from "crypto";
|
|
27628
|
-
var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, UPDATE_NAME_PATTERN, UPDATE_PUBLIC_KEY_PATTERN, ENVIRONMENT_NAME_PATTERN, DEFAULT_UPDATE_BOOT_TIMEOUT_MS = 20000, MINIMUM_UPDATE_BOOT_TIMEOUT_MS = 5000, MAXIMUM_UPDATE_BOOT_TIMEOUT_MS = 120000, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field) => {
|
|
27628
|
+
var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, UPDATE_NAME_PATTERN, UPDATE_PUBLIC_KEY_PATTERN, ENVIRONMENT_NAME_PATTERN, DEFAULT_UPDATE_BOOT_TIMEOUT_MS = 20000, DEFAULT_UPDATE_HEALTH_FAILURE_RATE = 0.2, DEFAULT_UPDATE_HEALTH_MINIMUM_REPORTS = 20, MINIMUM_UPDATE_BOOT_TIMEOUT_MS = 5000, MAXIMUM_UPDATE_BOOT_TIMEOUT_MS = 120000, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field) => {
|
|
27629
27629
|
const root = resolve44(projectRoot);
|
|
27630
27630
|
const path = resolve44(root, value);
|
|
27631
27631
|
if (path !== root && !path.startsWith(`${root}/`)) {
|
|
@@ -27819,6 +27819,20 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
27819
27819
|
if (!ENVIRONMENT_NAME_PATTERN.test(expoPrivateKeyEnv))
|
|
27820
27820
|
throw new TypeError("mobile.updates.server.expoPrivateKeyEnv must be a valid environment variable name.");
|
|
27821
27821
|
const autoMount = config.updates.server?.autoMount ?? true;
|
|
27822
|
+
const configuredHealth = config.updates.server?.health;
|
|
27823
|
+
let health;
|
|
27824
|
+
if (configuredHealth !== false) {
|
|
27825
|
+
const failureRate = configuredHealth?.failureRate ?? DEFAULT_UPDATE_HEALTH_FAILURE_RATE;
|
|
27826
|
+
const minimumReports = configuredHealth?.minimumReports ?? DEFAULT_UPDATE_HEALTH_MINIMUM_REPORTS;
|
|
27827
|
+
const secretEnv = requireText(configuredHealth?.secretEnv ?? "ABSOLUTE_MOBILE_UPDATE_HEALTH_SECRET", "mobile.updates.server.health.secretEnv");
|
|
27828
|
+
if (!Number.isFinite(failureRate) || failureRate <= 0 || failureRate > 1)
|
|
27829
|
+
throw new TypeError("mobile.updates.server.health.failureRate must be greater than 0 and at most 1.");
|
|
27830
|
+
if (!Number.isSafeInteger(minimumReports) || minimumReports < 1)
|
|
27831
|
+
throw new TypeError("mobile.updates.server.health.minimumReports must be a positive integer.");
|
|
27832
|
+
if (!ENVIRONMENT_NAME_PATTERN.test(secretEnv))
|
|
27833
|
+
throw new TypeError("mobile.updates.server.health.secretEnv must be a valid environment variable name.");
|
|
27834
|
+
health = { failureRate, minimumReports, secretEnv };
|
|
27835
|
+
}
|
|
27822
27836
|
if (autoMount) {
|
|
27823
27837
|
const manifest = new URL(updates.manifestUrl);
|
|
27824
27838
|
if (manifest.origin !== productionOrigin)
|
|
@@ -27851,7 +27865,12 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
27851
27865
|
throw new TypeError(`mobile.updates.server.expoCodeSigningKeys.${keyId} certificate is not currently valid.`);
|
|
27852
27866
|
expoCodeSigningKeys[keyId] = { certificatePem, privateKeyEnv };
|
|
27853
27867
|
}
|
|
27854
|
-
return {
|
|
27868
|
+
return {
|
|
27869
|
+
autoMount,
|
|
27870
|
+
expoCodeSigningKeys,
|
|
27871
|
+
...health ? { health } : {},
|
|
27872
|
+
registryModule
|
|
27873
|
+
};
|
|
27855
27874
|
}, validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
|
|
27856
27875
|
if (segment === "*" && (index !== count - 1 || count === 1)) {
|
|
27857
27876
|
throw new TypeError(`mobile.routes.native route ${path} must use * once, as the final segment after a static or parameterized prefix.`);
|
|
@@ -28472,15 +28491,17 @@ __export(exports_mobileUpdate, {
|
|
|
28472
28491
|
});
|
|
28473
28492
|
import {
|
|
28474
28493
|
createHash as createHash6,
|
|
28494
|
+
createHmac,
|
|
28475
28495
|
createPrivateKey,
|
|
28476
28496
|
createPublicKey as createPublicKey2,
|
|
28477
28497
|
sign,
|
|
28498
|
+
timingSafeEqual,
|
|
28478
28499
|
verify,
|
|
28479
28500
|
X509Certificate as X509Certificate2
|
|
28480
28501
|
} from "crypto";
|
|
28481
28502
|
import { readFile as readFile9, stat as stat3 } from "fs/promises";
|
|
28482
28503
|
import path from "path";
|
|
28483
|
-
var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, DAY_MS, DEFAULT_MIN_AGE_MS, DEFAULT_GRACE_PERIOD_MS, DEFAULT_RETAIN_RECENT = 5, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", MobileUpdateRegistryError, object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field) => {
|
|
28504
|
+
var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, DAY_MS, DEFAULT_MIN_AGE_MS, DEFAULT_GRACE_PERIOD_MS, DEFAULT_RETAIN_RECENT = 5, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", HEALTH_TOKEN_VERSION = 1, HEALTH_KINDS, FAILURE_HEALTH_KINDS, MobileUpdateRegistryError, object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field) => {
|
|
28484
28505
|
if (typeof value !== "string" || value.length === 0)
|
|
28485
28506
|
throw new MobileUpdateRegistryError(`Mobile update ${field} is invalid`);
|
|
28486
28507
|
return value;
|
|
@@ -28640,15 +28661,43 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
28640
28661
|
return true;
|
|
28641
28662
|
const value = createHash6("sha256").update(`${input.appId}\x00${input.channel}\x00${input.releaseId}\x00${input.installationId}`).digest().readUInt32BE(0);
|
|
28642
28663
|
return value / 4294967296 < input.rollout;
|
|
28664
|
+
}, base64Url = (value) => Buffer.from(value).toString("base64url"), healthPromotionId = (channel) => digest(new TextEncoder().encode(`${channel.appId}\x00${channel.channel}\x00${channel.releaseId ?? "embedded"}\x00${channel.promotedAt}`)), finiteMetric = (value, field) => {
|
|
28665
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
28666
|
+
throw new MobileUpdateRegistryError(`Mobile update health ${field} is invalid`);
|
|
28667
|
+
return value;
|
|
28668
|
+
}, parseHealthTransfer = (value) => {
|
|
28669
|
+
if (value === undefined)
|
|
28670
|
+
return;
|
|
28671
|
+
if (!object3(value))
|
|
28672
|
+
throw new MobileUpdateRegistryError("Mobile update health transfer is invalid");
|
|
28673
|
+
return {
|
|
28674
|
+
avoidedBytes: finiteMetric(value.avoidedBytes, "avoidedBytes"),
|
|
28675
|
+
downloadedBytes: finiteMetric(value.downloadedBytes, "downloadedBytes"),
|
|
28676
|
+
durationMs: finiteMetric(value.durationMs, "durationMs"),
|
|
28677
|
+
resumedBytes: finiteMetric(value.resumedBytes, "resumedBytes"),
|
|
28678
|
+
reusedBytes: finiteMetric(value.reusedBytes, "reusedBytes"),
|
|
28679
|
+
throughputBytesPerSecond: finiteMetric(value.throughputBytesPerSecond, "throughputBytesPerSecond")
|
|
28680
|
+
};
|
|
28643
28681
|
}, createMobileUpdateRegistry = (options) => {
|
|
28644
28682
|
const prefix = normalizedPrefix(options.prefix ?? DEFAULT_PREFIX);
|
|
28645
28683
|
const clock = options.clock ?? (() => new Date);
|
|
28684
|
+
const health = options.health;
|
|
28685
|
+
if (health && health.secret.length < 32)
|
|
28686
|
+
throw new MobileUpdateRegistryError("Mobile update health secret must contain at least 32 characters");
|
|
28687
|
+
if (health && !options.store.list)
|
|
28688
|
+
throw new MobileUpdateRegistryError("Mobile update health requires storage lifecycle listing");
|
|
28689
|
+
const minimumReports = health?.autoPause?.minimumReports ?? 20;
|
|
28690
|
+
const failureThreshold = health?.autoPause?.failureRate ?? 0.2;
|
|
28691
|
+
if (health && (!Number.isSafeInteger(minimumReports) || minimumReports < 1 || failureThreshold <= 0 || failureThreshold > 1))
|
|
28692
|
+
throw new MobileUpdateRegistryError("Mobile update health auto-pause policy is invalid");
|
|
28646
28693
|
const root = (appId) => `${prefix}/${appHash(appId)}`;
|
|
28647
28694
|
const releaseRoot = (manifest) => `${root(manifest.appId)}/releases/${manifest.releaseId}`;
|
|
28648
28695
|
const manifestKey = (manifest) => `${releaseRoot(manifest)}/update.json`;
|
|
28649
28696
|
const fileKey = (manifest, file5) => `${releaseRoot(manifest)}/files/${file5.path}`;
|
|
28650
28697
|
const contentBlobKey = (appId, sha256) => `${root(appId)}/blobs/${sha256}`;
|
|
28651
28698
|
const tombstoneKey = (appId, releaseId) => `${root(appId)}/gc/${releaseId}.json`;
|
|
28699
|
+
const healthRoot = (appId, promotionId, releaseId) => `${root(appId)}/health/${promotionId}/${releaseId}`;
|
|
28700
|
+
const pauseKey = (appId, promotionId, releaseId) => `${healthRoot(appId, promotionId, releaseId)}/paused.json`;
|
|
28652
28701
|
const channelKey = (appId, channel) => {
|
|
28653
28702
|
if (!APP_ID.test(appId) || !NAME.test(channel))
|
|
28654
28703
|
throw new MobileUpdateRegistryError("Mobile update channel identity is invalid");
|
|
@@ -28677,6 +28726,38 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
28677
28726
|
throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
|
|
28678
28727
|
return value;
|
|
28679
28728
|
};
|
|
28729
|
+
const signHealthToken = (payload) => {
|
|
28730
|
+
if (!health)
|
|
28731
|
+
return null;
|
|
28732
|
+
const encoded = base64Url(JSON.stringify(payload));
|
|
28733
|
+
const signature = createHmac("sha256", health.secret).update(encoded).digest("base64url");
|
|
28734
|
+
return `${encoded}.${signature}`;
|
|
28735
|
+
};
|
|
28736
|
+
const verifyHealthToken = (token) => {
|
|
28737
|
+
if (!health)
|
|
28738
|
+
throw new MobileUpdateRegistryError("Mobile update health reporting is not configured");
|
|
28739
|
+
const [encoded, provided, extra] = token.split(".");
|
|
28740
|
+
if (!encoded || !provided || extra)
|
|
28741
|
+
throw new MobileUpdateRegistryError("Mobile update health token is invalid");
|
|
28742
|
+
const expected = createHmac("sha256", health.secret).update(encoded).digest();
|
|
28743
|
+
let actual;
|
|
28744
|
+
try {
|
|
28745
|
+
actual = Buffer.from(provided, "base64url");
|
|
28746
|
+
} catch {
|
|
28747
|
+
actual = Buffer.alloc(0);
|
|
28748
|
+
}
|
|
28749
|
+
if (actual.byteLength !== expected.byteLength || !timingSafeEqual(actual, expected))
|
|
28750
|
+
throw new MobileUpdateRegistryError("Mobile update health token is invalid");
|
|
28751
|
+
let value;
|
|
28752
|
+
try {
|
|
28753
|
+
value = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
28754
|
+
} catch {
|
|
28755
|
+
throw new MobileUpdateRegistryError("Mobile update health token is invalid");
|
|
28756
|
+
}
|
|
28757
|
+
if (!object3(value) || value.format !== HEALTH_TOKEN_VERSION || typeof value.appId !== "string" || typeof value.channel !== "string" || typeof value.installationId !== "string" || typeof value.promotionId !== "string" || typeof value.releaseId !== "string" || typeof value.runtimeFingerprint !== "string")
|
|
28758
|
+
throw new MobileUpdateRegistryError("Mobile update health token is invalid");
|
|
28759
|
+
return value;
|
|
28760
|
+
};
|
|
28680
28761
|
const assertNotMarked = async (appId, releaseId) => {
|
|
28681
28762
|
if (!APP_ID.test(appId) || !RELEASE.test(releaseId))
|
|
28682
28763
|
throw new MobileUpdateRegistryError("Mobile update release identity is invalid");
|
|
@@ -28731,13 +28812,15 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
28731
28812
|
const channel = await readChannel(input.appId, input.channel);
|
|
28732
28813
|
if (!channel?.releaseId)
|
|
28733
28814
|
return { status: "empty" };
|
|
28734
|
-
|
|
28815
|
+
let selected = rolloutMember({
|
|
28735
28816
|
appId: input.appId,
|
|
28736
28817
|
channel: input.channel,
|
|
28737
28818
|
installationId: input.installationId,
|
|
28738
28819
|
releaseId: channel.releaseId,
|
|
28739
28820
|
rollout: channel.rollout
|
|
28740
28821
|
}) ? channel.releaseId : channel.fallbackReleaseId;
|
|
28822
|
+
if (health && selected === channel.releaseId && await options.store.head(pauseKey(input.appId, healthPromotionId(channel), channel.releaseId)))
|
|
28823
|
+
selected = channel.fallbackReleaseId;
|
|
28741
28824
|
if (!selected)
|
|
28742
28825
|
return { status: "empty" };
|
|
28743
28826
|
const release = await readManifest2(input.appId, selected);
|
|
@@ -28753,6 +28836,163 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
28753
28836
|
status: "selected"
|
|
28754
28837
|
};
|
|
28755
28838
|
};
|
|
28839
|
+
const issueUpdateHealthToken = async (input) => {
|
|
28840
|
+
if (!health)
|
|
28841
|
+
return null;
|
|
28842
|
+
const channel = await readChannel(input.appId, input.channel);
|
|
28843
|
+
if (!channel?.releaseId || channel.releaseId !== input.releaseId)
|
|
28844
|
+
return null;
|
|
28845
|
+
const resolution = await resolveUpdateState(input);
|
|
28846
|
+
if (resolution.status !== "selected" || resolution.manifest.releaseId !== input.releaseId)
|
|
28847
|
+
return null;
|
|
28848
|
+
return signHealthToken({
|
|
28849
|
+
appId: input.appId,
|
|
28850
|
+
channel: input.channel,
|
|
28851
|
+
format: HEALTH_TOKEN_VERSION,
|
|
28852
|
+
installationId: input.installationId,
|
|
28853
|
+
promotionId: healthPromotionId(channel),
|
|
28854
|
+
releaseId: input.releaseId,
|
|
28855
|
+
runtimeFingerprint: input.runtimeFingerprint
|
|
28856
|
+
});
|
|
28857
|
+
};
|
|
28858
|
+
const inspectUpdateHealth = async (input) => {
|
|
28859
|
+
if (!health)
|
|
28860
|
+
throw new MobileUpdateRegistryError("Mobile update health reporting is not configured");
|
|
28861
|
+
const channel = await readChannel(input.appId, input.channel);
|
|
28862
|
+
const releaseId = input.releaseId ?? channel?.releaseId;
|
|
28863
|
+
if (!channel || !releaseId || channel.releaseId !== releaseId)
|
|
28864
|
+
return null;
|
|
28865
|
+
const promotionId = healthPromotionId(channel);
|
|
28866
|
+
const prefix2 = `${healthRoot(input.appId, promotionId, releaseId)}/events/`;
|
|
28867
|
+
const objects = [];
|
|
28868
|
+
const cursors = new Set;
|
|
28869
|
+
let cursor;
|
|
28870
|
+
do {
|
|
28871
|
+
const page = await options.store.list({
|
|
28872
|
+
...cursor ? { cursor } : {},
|
|
28873
|
+
prefix: prefix2
|
|
28874
|
+
});
|
|
28875
|
+
objects.push(...page.objects);
|
|
28876
|
+
if (!page.truncated)
|
|
28877
|
+
break;
|
|
28878
|
+
if (!page.cursor || cursors.has(page.cursor))
|
|
28879
|
+
throw new MobileUpdateRegistryError("Mobile update health storage returned an invalid cursor");
|
|
28880
|
+
cursors.add(page.cursor);
|
|
28881
|
+
cursor = page.cursor;
|
|
28882
|
+
} while (true);
|
|
28883
|
+
const installations = new Set;
|
|
28884
|
+
const byKind = new Map([...HEALTH_KINDS].map((kind) => [
|
|
28885
|
+
kind,
|
|
28886
|
+
new Set
|
|
28887
|
+
]));
|
|
28888
|
+
const transfer = {
|
|
28889
|
+
avoidedBytes: 0,
|
|
28890
|
+
downloadedBytes: 0,
|
|
28891
|
+
durationMs: 0,
|
|
28892
|
+
resumedBytes: 0,
|
|
28893
|
+
reusedBytes: 0,
|
|
28894
|
+
throughputBytesPerSecond: 0
|
|
28895
|
+
};
|
|
28896
|
+
for (const item of objects) {
|
|
28897
|
+
const bytes = await options.store.get(item.key);
|
|
28898
|
+
if (!bytes)
|
|
28899
|
+
continue;
|
|
28900
|
+
const head = await options.store.head(item.key);
|
|
28901
|
+
if (!head || head.size !== bytes.byteLength || head.metadata?.sha256 !== digest(bytes))
|
|
28902
|
+
throw new MobileUpdateRegistryError("Stored mobile update health evidence integrity failed");
|
|
28903
|
+
const value = decode(bytes);
|
|
28904
|
+
if (!object3(value) || typeof value.installationHash !== "string" || !HEALTH_KINDS.has(String(value.kind)))
|
|
28905
|
+
throw new MobileUpdateRegistryError("Stored mobile update health evidence is invalid");
|
|
28906
|
+
const kind = value.kind;
|
|
28907
|
+
installations.add(value.installationHash);
|
|
28908
|
+
byKind.get(kind).add(value.installationHash);
|
|
28909
|
+
if (kind === "downloaded" && object3(value.transfer)) {
|
|
28910
|
+
const parsed = parseHealthTransfer(value.transfer);
|
|
28911
|
+
for (const key of Object.keys(transfer))
|
|
28912
|
+
transfer[key] += parsed[key];
|
|
28913
|
+
}
|
|
28914
|
+
}
|
|
28915
|
+
const failures = new Set([
|
|
28916
|
+
...byKind.get("quarantined"),
|
|
28917
|
+
...byKind.get("rolled-back")
|
|
28918
|
+
]);
|
|
28919
|
+
const terminals = new Set([...byKind.get("activated"), ...failures]);
|
|
28920
|
+
const failureRate = terminals.size === 0 ? 0 : failures.size / terminals.size;
|
|
28921
|
+
return {
|
|
28922
|
+
activated: byKind.get("activated").size,
|
|
28923
|
+
appId: input.appId,
|
|
28924
|
+
channel: input.channel,
|
|
28925
|
+
downloaded: byKind.get("downloaded").size,
|
|
28926
|
+
downloadFailed: byKind.get("download-failed").size,
|
|
28927
|
+
failureRate,
|
|
28928
|
+
failures: failures.size,
|
|
28929
|
+
paused: Boolean(await options.store.head(pauseKey(input.appId, promotionId, releaseId))),
|
|
28930
|
+
promotionId,
|
|
28931
|
+
quarantined: byKind.get("quarantined").size,
|
|
28932
|
+
releaseId,
|
|
28933
|
+
reportedInstallations: installations.size,
|
|
28934
|
+
rolledBack: byKind.get("rolled-back").size,
|
|
28935
|
+
rollout: channel.rollout,
|
|
28936
|
+
terminalReports: terminals.size,
|
|
28937
|
+
transfer
|
|
28938
|
+
};
|
|
28939
|
+
};
|
|
28940
|
+
const recordUpdateHealth = async (input) => {
|
|
28941
|
+
const payload = verifyHealthToken(input.token);
|
|
28942
|
+
if (payload.appId !== input.appId || payload.channel !== input.channel || payload.installationId !== input.installationId || payload.releaseId !== input.releaseId || payload.runtimeFingerprint !== input.runtimeFingerprint || !HEALTH_KINDS.has(input.kind) || input.reason !== undefined && input.reason !== "boot-interrupted" && input.reason !== "boot-timeout")
|
|
28943
|
+
throw new MobileUpdateRegistryError("Mobile update health evidence does not match its token");
|
|
28944
|
+
const release = await readManifest2(input.appId, input.releaseId);
|
|
28945
|
+
if (!release || release.manifest.runtimeFingerprint !== input.runtimeFingerprint)
|
|
28946
|
+
throw new MobileUpdateRegistryError("Mobile update health release is invalid");
|
|
28947
|
+
const activeChannel = await readChannel(input.appId, input.channel);
|
|
28948
|
+
if (!activeChannel || activeChannel.releaseId !== input.releaseId || healthPromotionId(activeChannel) !== payload.promotionId)
|
|
28949
|
+
throw new MobileUpdateRegistryError("Mobile update health promotion is no longer active");
|
|
28950
|
+
const transfer = parseHealthTransfer(input.transfer);
|
|
28951
|
+
const installationHash = createHmac("sha256", health.secret).update(input.installationId).digest("hex");
|
|
28952
|
+
const evidence = {
|
|
28953
|
+
format: 1,
|
|
28954
|
+
installationHash,
|
|
28955
|
+
kind: input.kind,
|
|
28956
|
+
observedAt: clock().toISOString(),
|
|
28957
|
+
...input.reason ? { reason: input.reason } : {},
|
|
28958
|
+
...transfer ? { transfer } : {}
|
|
28959
|
+
};
|
|
28960
|
+
const bytes = json(evidence);
|
|
28961
|
+
await options.store.put(`${healthRoot(input.appId, payload.promotionId, input.releaseId)}/events/${installationHash}/${input.kind}.json`, bytes, {
|
|
28962
|
+
cacheControl: "no-store",
|
|
28963
|
+
contentType: "application/json",
|
|
28964
|
+
maxBytes: bytes.byteLength,
|
|
28965
|
+
metadata: { kind: input.kind, sha256: digest(bytes) }
|
|
28966
|
+
});
|
|
28967
|
+
let report = await inspectUpdateHealth({
|
|
28968
|
+
appId: input.appId,
|
|
28969
|
+
channel: input.channel,
|
|
28970
|
+
releaseId: input.releaseId
|
|
28971
|
+
});
|
|
28972
|
+
if (!report)
|
|
28973
|
+
throw new MobileUpdateRegistryError("Mobile update health promotion is no longer active");
|
|
28974
|
+
if (FAILURE_HEALTH_KINDS.has(input.kind) && report.terminalReports >= minimumReports && report.failureRate >= failureThreshold && !report.paused) {
|
|
28975
|
+
const marker = json({
|
|
28976
|
+
appId: input.appId,
|
|
28977
|
+
channel: input.channel,
|
|
28978
|
+
failureRate: report.failureRate,
|
|
28979
|
+
failures: report.failures,
|
|
28980
|
+
format: 1,
|
|
28981
|
+
pausedAt: clock().toISOString(),
|
|
28982
|
+
promotionId: payload.promotionId,
|
|
28983
|
+
releaseId: input.releaseId,
|
|
28984
|
+
reports: report.terminalReports
|
|
28985
|
+
});
|
|
28986
|
+
await options.store.put(pauseKey(input.appId, payload.promotionId, input.releaseId), marker, {
|
|
28987
|
+
cacheControl: "no-store",
|
|
28988
|
+
contentType: "application/json",
|
|
28989
|
+
maxBytes: marker.byteLength,
|
|
28990
|
+
metadata: { releaseid: input.releaseId, sha256: digest(marker) }
|
|
28991
|
+
});
|
|
28992
|
+
report = { ...report, paused: true };
|
|
28993
|
+
}
|
|
28994
|
+
return report;
|
|
28995
|
+
};
|
|
28756
28996
|
const retentionValues = (input) => {
|
|
28757
28997
|
if (!APP_ID.test(input.appId))
|
|
28758
28998
|
throw new MobileUpdateRegistryError("Mobile update appId is invalid");
|
|
@@ -28995,6 +29235,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
28995
29235
|
return result;
|
|
28996
29236
|
};
|
|
28997
29237
|
return {
|
|
29238
|
+
...health ? { inspectUpdateHealth, issueUpdateHealthToken, recordUpdateHealth } : {},
|
|
28998
29239
|
inspectUpdateStorage: async (input) => (await inventory(input)).report,
|
|
28999
29240
|
pruneUpdates,
|
|
29000
29241
|
publishUpdate: async (input) => {
|
|
@@ -29270,24 +29511,69 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
29270
29511
|
const expoCodeSigning = resolveExpoCodeSigning(options.expoCodeSigning);
|
|
29271
29512
|
return async (request) => {
|
|
29272
29513
|
const origin = request.headers.get("origin");
|
|
29273
|
-
const cors = origin && allowedOrigins.has(origin) ? {
|
|
29514
|
+
const cors = origin && allowedOrigins.has(origin) ? {
|
|
29515
|
+
"access-control-allow-origin": origin,
|
|
29516
|
+
"access-control-expose-headers": "content-range,etag,x-absolute-mobile-health-token",
|
|
29517
|
+
vary: "Origin"
|
|
29518
|
+
} : {};
|
|
29274
29519
|
if (request.method === "OPTIONS") {
|
|
29275
29520
|
if (!origin || !allowedOrigins.has(origin))
|
|
29276
29521
|
return new Response(null, { status: 403 });
|
|
29277
29522
|
return new Response(null, {
|
|
29278
29523
|
headers: {
|
|
29279
29524
|
...cors,
|
|
29280
|
-
"access-control-allow-headers": "x-absolute-mobile-app,x-absolute-mobile-channel,x-absolute-mobile-installation,x-absolute-mobile-release,x-absolute-mobile-runtime",
|
|
29281
|
-
"access-control-allow-methods": "GET,OPTIONS",
|
|
29525
|
+
"access-control-allow-headers": "content-type,if-range,range,x-absolute-mobile-app,x-absolute-mobile-channel,x-absolute-mobile-health-token,x-absolute-mobile-installation,x-absolute-mobile-release,x-absolute-mobile-runtime",
|
|
29526
|
+
"access-control-allow-methods": "GET,POST,OPTIONS",
|
|
29282
29527
|
"access-control-max-age": "600"
|
|
29283
29528
|
},
|
|
29284
29529
|
status: 204
|
|
29285
29530
|
});
|
|
29286
29531
|
}
|
|
29287
|
-
if (request.method !== "GET")
|
|
29288
|
-
return new Response(null, { status: 405 });
|
|
29289
29532
|
const pathname = new URL(request.url).pathname.replace(/^\/+/, "");
|
|
29290
29533
|
const relative19 = pathname.startsWith(`${route}/`) ? pathname.slice(route.length + 1) : "";
|
|
29534
|
+
if (request.method === "POST" && relative19 === "health") {
|
|
29535
|
+
if (!options.registry.recordUpdateHealth)
|
|
29536
|
+
return new Response(null, { status: 404 });
|
|
29537
|
+
const appId = request.headers.get("x-absolute-mobile-app");
|
|
29538
|
+
const channel = request.headers.get("x-absolute-mobile-channel");
|
|
29539
|
+
const installationId = request.headers.get("x-absolute-mobile-installation");
|
|
29540
|
+
const runtimeFingerprint = request.headers.get("x-absolute-mobile-runtime");
|
|
29541
|
+
const token = request.headers.get("x-absolute-mobile-health-token");
|
|
29542
|
+
const declared = Number(request.headers.get("content-length"));
|
|
29543
|
+
if (appId !== options.appId || channel !== options.channel || !installationId || !runtimeFingerprint || !token || Number.isFinite(declared) && declared > 4096)
|
|
29544
|
+
return new Response(null, { status: 400 });
|
|
29545
|
+
const bodyBytes = new Uint8Array(await request.arrayBuffer());
|
|
29546
|
+
if (bodyBytes.byteLength > 4096)
|
|
29547
|
+
return new Response(null, { status: 413 });
|
|
29548
|
+
let body;
|
|
29549
|
+
try {
|
|
29550
|
+
body = JSON.parse(new TextDecoder().decode(bodyBytes));
|
|
29551
|
+
} catch {
|
|
29552
|
+
return new Response(null, { status: 400 });
|
|
29553
|
+
}
|
|
29554
|
+
if (!object3(body) || typeof body.releaseId !== "string" || typeof body.kind !== "string")
|
|
29555
|
+
return new Response(null, { status: 400 });
|
|
29556
|
+
try {
|
|
29557
|
+
const report = await options.registry.recordUpdateHealth({
|
|
29558
|
+
appId,
|
|
29559
|
+
channel,
|
|
29560
|
+
installationId,
|
|
29561
|
+
kind: body.kind,
|
|
29562
|
+
...body.reason === "boot-interrupted" || body.reason === "boot-timeout" ? { reason: body.reason } : {},
|
|
29563
|
+
releaseId: body.releaseId,
|
|
29564
|
+
runtimeFingerprint,
|
|
29565
|
+
token,
|
|
29566
|
+
...object3(body.transfer) ? { transfer: body.transfer } : {}
|
|
29567
|
+
});
|
|
29568
|
+
return Response.json({ paused: report.paused }, { headers: { ...cors, "cache-control": "no-store" }, status: 202 });
|
|
29569
|
+
} catch (error) {
|
|
29570
|
+
if (error instanceof MobileUpdateRegistryError)
|
|
29571
|
+
return new Response(null, { status: 403 });
|
|
29572
|
+
throw error;
|
|
29573
|
+
}
|
|
29574
|
+
}
|
|
29575
|
+
if (request.method !== "GET")
|
|
29576
|
+
return new Response(null, { status: 405 });
|
|
29291
29577
|
if (relative19 === "update.json") {
|
|
29292
29578
|
const expoProtocolVersion = request.headers.get("expo-protocol-version");
|
|
29293
29579
|
const expoProtocol = expoProtocolVersion !== null;
|
|
@@ -29313,6 +29599,13 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
29313
29599
|
installationId,
|
|
29314
29600
|
runtimeFingerprint
|
|
29315
29601
|
});
|
|
29602
|
+
const healthToken = selected && options.registry.issueUpdateHealthToken ? await options.registry.issueUpdateHealthToken({
|
|
29603
|
+
appId,
|
|
29604
|
+
channel,
|
|
29605
|
+
installationId,
|
|
29606
|
+
releaseId: selected.manifest.releaseId,
|
|
29607
|
+
runtimeFingerprint
|
|
29608
|
+
}) : null;
|
|
29316
29609
|
if (expoProtocol) {
|
|
29317
29610
|
let requestedCodeSigning;
|
|
29318
29611
|
try {
|
|
@@ -29364,6 +29657,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
29364
29657
|
extra: {
|
|
29365
29658
|
absolutejs: {
|
|
29366
29659
|
channel: selected.manifest.channel,
|
|
29660
|
+
...healthToken ? { healthToken } : {},
|
|
29367
29661
|
releaseId: selected.manifest.releaseId
|
|
29368
29662
|
},
|
|
29369
29663
|
expoClient: descriptor.expoConfig
|
|
@@ -29391,7 +29685,8 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
29391
29685
|
headers: {
|
|
29392
29686
|
...cors,
|
|
29393
29687
|
"cache-control": "no-store",
|
|
29394
|
-
etag: `"${selected.manifest.releaseId}"
|
|
29688
|
+
etag: `"${selected.manifest.releaseId}"`,
|
|
29689
|
+
...healthToken ? { "x-absolute-mobile-health-token": healthToken } : {}
|
|
29395
29690
|
}
|
|
29396
29691
|
});
|
|
29397
29692
|
}
|
|
@@ -29405,14 +29700,42 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
29405
29700
|
});
|
|
29406
29701
|
if (!file5)
|
|
29407
29702
|
return new Response(null, { status: 404 });
|
|
29408
|
-
|
|
29703
|
+
const etag = `"${file5.file.sha256}"`;
|
|
29704
|
+
const range = request.headers.get("range");
|
|
29705
|
+
const useRange = range !== null && (!request.headers.has("if-range") || request.headers.get("if-range") === etag);
|
|
29706
|
+
let contents = file5.bytes;
|
|
29707
|
+
let status = 200;
|
|
29708
|
+
let contentRange;
|
|
29709
|
+
if (useRange) {
|
|
29710
|
+
const parsed = /^bytes=(\d+)-(\d*)$/.exec(range);
|
|
29711
|
+
const start = parsed?.[1] === undefined ? NaN : Number(parsed[1]);
|
|
29712
|
+
const requestedEnd = parsed?.[2] === undefined || parsed[2] === "" ? file5.bytes.byteLength - 1 : Number(parsed[2]);
|
|
29713
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(requestedEnd) || start < 0 || start >= file5.bytes.byteLength || requestedEnd < start)
|
|
29714
|
+
return new Response(null, {
|
|
29715
|
+
headers: {
|
|
29716
|
+
...cors,
|
|
29717
|
+
"accept-ranges": "bytes",
|
|
29718
|
+
"content-range": `bytes */${file5.bytes.byteLength}`,
|
|
29719
|
+
etag
|
|
29720
|
+
},
|
|
29721
|
+
status: 416
|
|
29722
|
+
});
|
|
29723
|
+
const end = Math.min(requestedEnd, file5.bytes.byteLength - 1);
|
|
29724
|
+
contents = file5.bytes.slice(start, end + 1);
|
|
29725
|
+
status = 206;
|
|
29726
|
+
contentRange = `bytes ${start}-${end}/${file5.bytes.byteLength}`;
|
|
29727
|
+
}
|
|
29728
|
+
return new Response(new Blob([new Uint8Array(contents).buffer]), {
|
|
29409
29729
|
headers: {
|
|
29410
29730
|
...cors,
|
|
29731
|
+
"accept-ranges": "bytes",
|
|
29411
29732
|
"cache-control": "public, max-age=31536000, immutable",
|
|
29412
|
-
"content-length": String(
|
|
29733
|
+
"content-length": String(contents.byteLength),
|
|
29734
|
+
...contentRange ? { "content-range": contentRange } : {},
|
|
29413
29735
|
"content-type": expoContentType(file5.file.path.includes(".") ? file5.file.path.slice(file5.file.path.lastIndexOf(".") + 1) : undefined, false),
|
|
29414
|
-
etag
|
|
29415
|
-
}
|
|
29736
|
+
etag
|
|
29737
|
+
},
|
|
29738
|
+
status
|
|
29416
29739
|
});
|
|
29417
29740
|
};
|
|
29418
29741
|
};
|
|
@@ -29426,6 +29749,14 @@ var init_mobileUpdate = __esm(() => {
|
|
|
29426
29749
|
RELEASE = /^amu_[a-f0-9]{64}$/;
|
|
29427
29750
|
APP_ID = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
|
|
29428
29751
|
NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
29752
|
+
HEALTH_KINDS = new Set([
|
|
29753
|
+
"activated",
|
|
29754
|
+
"downloaded",
|
|
29755
|
+
"download-failed",
|
|
29756
|
+
"quarantined",
|
|
29757
|
+
"rolled-back"
|
|
29758
|
+
]);
|
|
29759
|
+
FAILURE_HEALTH_KINDS = new Set(["quarantined", "rolled-back"]);
|
|
29429
29760
|
MobileUpdateRegistryError = class MobileUpdateRegistryError extends Error {
|
|
29430
29761
|
};
|
|
29431
29762
|
expoProtocolHeaders = {
|
|
@@ -29513,6 +29844,11 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
|
|
|
29513
29844
|
} catch (error) {
|
|
29514
29845
|
throw new TypeError(`Durable mobile update storage verification failed for ${module.metadata.provider}. Check the bucket, endpoint, credentials, and read/write/delete permissions.`, { cause: error });
|
|
29515
29846
|
}
|
|
29847
|
+
}, verifyHealthModule = (config, module) => {
|
|
29848
|
+
if (!config.updateServer?.health)
|
|
29849
|
+
return;
|
|
29850
|
+
if (typeof module.registry.inspectUpdateHealth !== "function" || typeof module.registry.issueUpdateHealthToken !== "function" || typeof module.registry.recordUpdateHealth !== "function")
|
|
29851
|
+
throw new TypeError("Mobile update fleet health is enabled but the registry is not provisioned for it. Run `absolute mobile update provision --force`.");
|
|
29516
29852
|
}, expoSigningOptions = (config) => {
|
|
29517
29853
|
if (!config.updates?.expoCodeSigning)
|
|
29518
29854
|
return;
|
|
@@ -29544,8 +29880,10 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
|
|
|
29544
29880
|
if (!updates || !server?.autoMount)
|
|
29545
29881
|
return new Elysia5({ name: "absolutejs-mobile-updates-disabled" });
|
|
29546
29882
|
const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, server.registryModule);
|
|
29547
|
-
if (options.production)
|
|
29883
|
+
if (options.production) {
|
|
29548
29884
|
await verifyDurableModule(module);
|
|
29885
|
+
verifyHealthModule(config, module);
|
|
29886
|
+
}
|
|
29549
29887
|
const manifest = new URL(updates.manifestUrl);
|
|
29550
29888
|
if (!manifest.pathname.endsWith("/update.json"))
|
|
29551
29889
|
throw new TypeError("Auto-mounted mobile update manifests must end in /update.json.");
|
|
@@ -29564,10 +29902,11 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
|
|
|
29564
29902
|
return;
|
|
29565
29903
|
const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, config.updateServer?.registryModule);
|
|
29566
29904
|
await verifyDurableModule(module);
|
|
29905
|
+
verifyHealthModule(config, module);
|
|
29567
29906
|
if (config.engine === "expo")
|
|
29568
29907
|
expoSigningOptions(config);
|
|
29569
29908
|
return module.metadata;
|
|
29570
|
-
}, publicKeysSource = (publicKeys) => JSON.stringify(publicKeys, null, "\t"),
|
|
29909
|
+
}, publicKeysSource = (publicKeys) => JSON.stringify(publicKeys, null, "\t"), renderAbsoluteMobileUpdateRegistryBase = (options) => {
|
|
29571
29910
|
const metadata = `export const absoluteMobileUpdateServer = {
|
|
29572
29911
|
format: 1,
|
|
29573
29912
|
provider: '${options.storage}',
|
|
@@ -29633,6 +29972,19 @@ export default createMobileUpdateRegistry({
|
|
|
29633
29972
|
store
|
|
29634
29973
|
});
|
|
29635
29974
|
`;
|
|
29975
|
+
}, renderAbsoluteMobileUpdateRegistry = (options) => {
|
|
29976
|
+
const source = renderAbsoluteMobileUpdateRegistryBase(options);
|
|
29977
|
+
if (!options.health)
|
|
29978
|
+
return source;
|
|
29979
|
+
const secret = options.storage === "local" ? `process.env.${options.health.secretEnv} ?? 'absolutejs-local-health-secret-not-for-production'` : `required('${options.health.secretEnv}')`;
|
|
29980
|
+
const health = ` health: {
|
|
29981
|
+
autoPause: { failureRate: ${options.health.failureRate}, minimumReports: ${options.health.minimumReports} },
|
|
29982
|
+
secret: ${secret}
|
|
29983
|
+
},
|
|
29984
|
+
`;
|
|
29985
|
+
return source.replace(`export default createMobileUpdateRegistry({
|
|
29986
|
+
`, `export default createMobileUpdateRegistry({
|
|
29987
|
+
${health}`);
|
|
29636
29988
|
}, writeAbsoluteMobileUpdateRegistry = async (options) => {
|
|
29637
29989
|
const path2 = projectPath(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
|
|
29638
29990
|
if (!options.force) {
|
|
@@ -29644,6 +29996,7 @@ export default createMobileUpdateRegistry({
|
|
|
29644
29996
|
}
|
|
29645
29997
|
await mkdir13(dirname31(path2), { recursive: true });
|
|
29646
29998
|
await Bun.write(path2, renderAbsoluteMobileUpdateRegistry({
|
|
29999
|
+
...options.health ? { health: options.health } : {},
|
|
29647
30000
|
publicKeys: options.publicKeys,
|
|
29648
30001
|
storage: options.storage
|
|
29649
30002
|
}));
|
|
@@ -37246,5 +37599,5 @@ export {
|
|
|
37246
37599
|
devBuild
|
|
37247
37600
|
};
|
|
37248
37601
|
|
|
37249
|
-
//# debugId=
|
|
37602
|
+
//# debugId=C5E73CF77BAB352E64756E2164756E21
|
|
37250
37603
|
//# sourceMappingURL=build.js.map
|