@absolutejs/absolute 0.20.0-beta.86 → 0.20.0-beta.87
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 +350 -33
- package/dist/build.js.map +5 -5
- package/dist/cli/{compile-e9cn3xpx.js → compile-5hchbcbd.js} +2 -2
- package/dist/cli/{config-z2kf085h.js → config-vy04mqce.js} +1 -1
- package/dist/cli/{dev-3hweza3s.js → dev-6shm8qsn.js} +1 -1
- package/dist/cli/{index-9rk7v35t.js → index-39j0dhq7.js} +2 -2
- package/dist/cli/{index-x48brywr.js → index-jqdt7841.js} +1 -1
- package/dist/cli/{index-3tsmbyze.js → index-rd2hbttb.js} +45 -0
- package/dist/cli/index.js +5 -5
- package/dist/cli/{mobile-2p3z33t1.js → mobile-c94a5y9k.js} +141 -23
- package/dist/cli/{start-7t186mhg.js → start-7jg0csqy.js} +3 -3
- package/dist/index.js +350 -33
- package/dist/index.js.map +5 -5
- package/dist/mobile/index.js +396 -41
- package/dist/mobile/index.js.map +6 -6
- package/dist/mobile/remoteMacAgentEntry.js +167 -167
- package/dist/src/mobile/config.d.ts +9 -0
- package/dist/src/mobile/updatePublisher.d.ts +67 -1
- package/dist/src/mobile/updateServer.d.ts +18 -0
- package/dist/types/build.d.ts +16 -0
- package/package.json +2 -2
package/dist/mobile/index.js
CHANGED
|
@@ -604,7 +604,7 @@ __export(exports_config, {
|
|
|
604
604
|
import { readFileSync as readFileSync2 } from "fs";
|
|
605
605
|
import { resolve as resolve6 } from "path";
|
|
606
606
|
import { createHash as createHash8, createPublicKey, X509Certificate as X509Certificate2 } from "crypto";
|
|
607
|
-
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, field2) => {
|
|
607
|
+
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, DEFAULT_UPDATE_ROLLOUT_FAILURE_RATE = 0.05, DEFAULT_UPDATE_ROLLOUT_OBSERVATION_MINUTES = 60, MINUTE_MS, DEFAULT_UPDATE_ROLLOUT_STAGES, MINIMUM_UPDATE_BOOT_TIMEOUT_MS = 5000, MAXIMUM_UPDATE_BOOT_TIMEOUT_MS = 120000, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field2) => {
|
|
608
608
|
const root = resolve6(projectRoot);
|
|
609
609
|
const path = resolve6(root, value);
|
|
610
610
|
if (path !== root && !path.startsWith(`${root}/`)) {
|
|
@@ -812,6 +812,42 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
812
812
|
throw new TypeError("mobile.updates.server.health.secretEnv must be a valid environment variable name.");
|
|
813
813
|
health = { failureRate, minimumReports, secretEnv };
|
|
814
814
|
}
|
|
815
|
+
const configuredRollout = config.updates.server?.rollout;
|
|
816
|
+
let rollout;
|
|
817
|
+
if (configuredRollout !== undefined && configuredRollout !== false) {
|
|
818
|
+
if (!health)
|
|
819
|
+
throw new TypeError("mobile.updates.server.rollout requires fleet health to be enabled.");
|
|
820
|
+
if (configuredRollout.automatic !== undefined && typeof configuredRollout.automatic !== "boolean")
|
|
821
|
+
throw new TypeError("mobile.updates.server.rollout.automatic must be boolean.");
|
|
822
|
+
const configuredStages = configuredRollout.stages ?? DEFAULT_UPDATE_ROLLOUT_STAGES;
|
|
823
|
+
const stages = configuredStages.map((stage, index) => {
|
|
824
|
+
const previousStage = configuredStages[index - 1];
|
|
825
|
+
const maximumFailureRate = stage.maximumFailureRate ?? DEFAULT_UPDATE_ROLLOUT_FAILURE_RATE;
|
|
826
|
+
const minimumReports = stage.minimumReports ?? DEFAULT_UPDATE_HEALTH_MINIMUM_REPORTS;
|
|
827
|
+
const observationMinutes = stage.observationMinutes ?? DEFAULT_UPDATE_ROLLOUT_OBSERVATION_MINUTES;
|
|
828
|
+
const observationMs = observationMinutes * MINUTE_MS;
|
|
829
|
+
if (!Number.isFinite(stage.rollout) || stage.rollout <= 0 || stage.rollout > 1 || previousStage !== undefined && stage.rollout <= previousStage.rollout)
|
|
830
|
+
throw new TypeError("mobile.updates.server.rollout stages must be strictly increasing fractions greater than 0 and at most 1.");
|
|
831
|
+
if (!Number.isFinite(maximumFailureRate) || maximumFailureRate < 0 || maximumFailureRate >= health.failureRate)
|
|
832
|
+
throw new TypeError("mobile.updates.server.rollout maximumFailureRate must be non-negative and lower than the fleet-health pause rate.");
|
|
833
|
+
if (!Number.isSafeInteger(minimumReports) || minimumReports < health.minimumReports)
|
|
834
|
+
throw new TypeError("mobile.updates.server.rollout minimumReports must be an integer at least as large as the fleet-health minimumReports.");
|
|
835
|
+
if (!Number.isFinite(observationMinutes) || observationMinutes < 0 || !Number.isSafeInteger(observationMs))
|
|
836
|
+
throw new TypeError("mobile.updates.server.rollout observationMinutes must produce a non-negative whole number of milliseconds.");
|
|
837
|
+
return {
|
|
838
|
+
maximumFailureRate,
|
|
839
|
+
minimumReports,
|
|
840
|
+
observationMs,
|
|
841
|
+
rollout: stage.rollout
|
|
842
|
+
};
|
|
843
|
+
});
|
|
844
|
+
if (stages.length === 0 || stages.at(-1)?.rollout !== 1)
|
|
845
|
+
throw new TypeError("mobile.updates.server.rollout stages must end at rollout 1.");
|
|
846
|
+
rollout = {
|
|
847
|
+
automatic: configuredRollout.automatic ?? false,
|
|
848
|
+
stages
|
|
849
|
+
};
|
|
850
|
+
}
|
|
815
851
|
if (autoMount) {
|
|
816
852
|
const manifest = new URL(updates.manifestUrl);
|
|
817
853
|
if (manifest.origin !== productionOrigin)
|
|
@@ -848,6 +884,7 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
848
884
|
autoMount,
|
|
849
885
|
expoCodeSigningKeys,
|
|
850
886
|
...health ? { health } : {},
|
|
887
|
+
...rollout ? { rollout } : {},
|
|
851
888
|
registryModule
|
|
852
889
|
};
|
|
853
890
|
}, validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
|
|
@@ -941,6 +978,12 @@ var init_config = __esm(() => {
|
|
|
941
978
|
UPDATE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
942
979
|
UPDATE_PUBLIC_KEY_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/u;
|
|
943
980
|
ENVIRONMENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
981
|
+
MINUTE_MS = 60 * 1000;
|
|
982
|
+
DEFAULT_UPDATE_ROLLOUT_STAGES = [
|
|
983
|
+
{ minimumReports: 20, observationMinutes: 60, rollout: 0.05 },
|
|
984
|
+
{ minimumReports: 100, observationMinutes: 360, rollout: 0.25 },
|
|
985
|
+
{ minimumReports: 100, observationMinutes: 0, rollout: 1 }
|
|
986
|
+
];
|
|
944
987
|
HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
|
|
945
988
|
EXPO_RESERVED_ROUTE_PREFIXES = new Set([
|
|
946
989
|
"_expo",
|
|
@@ -19984,6 +20027,7 @@ import {
|
|
|
19984
20027
|
createHmac,
|
|
19985
20028
|
createPrivateKey,
|
|
19986
20029
|
createPublicKey as createPublicKey2,
|
|
20030
|
+
randomUUID as randomUUID5,
|
|
19987
20031
|
sign as sign2,
|
|
19988
20032
|
timingSafeEqual,
|
|
19989
20033
|
verify as verify2,
|
|
@@ -20120,7 +20164,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20120
20164
|
throw new MobileUpdateRegistryError("Mobile update channel is invalid");
|
|
20121
20165
|
const appId = text2(value.appId, "channel appId");
|
|
20122
20166
|
const channel = text2(value.channel, "channel");
|
|
20123
|
-
if (!APP_ID.test(appId) || !NAME.test(channel) || !iso(value.promotedAt) || typeof value.rollout !== "number" || value.rollout < 0 || value.rollout > 1 || value.releaseId !== undefined && (typeof value.releaseId !== "string" || !RELEASE.test(value.releaseId)) || value.fallbackReleaseId !== undefined && (typeof value.fallbackReleaseId !== "string" || !RELEASE.test(value.fallbackReleaseId)) || value.activationId !== undefined && (typeof value.activationId !== "string" || !HASH.test(value.activationId)) || value.activatedAt !== undefined && !iso(value.activatedAt) || value.activationId === undefined !== (value.activatedAt === undefined))
|
|
20167
|
+
if (!APP_ID.test(appId) || !NAME.test(channel) || !iso(value.promotedAt) || typeof value.rollout !== "number" || value.rollout < 0 || value.rollout > 1 || value.releaseId !== undefined && (typeof value.releaseId !== "string" || !RELEASE.test(value.releaseId)) || value.fallbackReleaseId !== undefined && (typeof value.fallbackReleaseId !== "string" || !RELEASE.test(value.fallbackReleaseId)) || value.promotionId !== undefined && (typeof value.promotionId !== "string" || !HASH.test(value.promotionId)) || value.activationId !== undefined && (typeof value.activationId !== "string" || !HASH.test(value.activationId)) || value.activatedAt !== undefined && !iso(value.activatedAt) || value.activationId === undefined !== (value.activatedAt === undefined))
|
|
20124
20168
|
throw new MobileUpdateRegistryError("Mobile update channel is invalid");
|
|
20125
20169
|
return {
|
|
20126
20170
|
...value.activationId ? { activationId: value.activationId } : {},
|
|
@@ -20129,6 +20173,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20129
20173
|
channel,
|
|
20130
20174
|
...value.fallbackReleaseId ? { fallbackReleaseId: value.fallbackReleaseId } : {},
|
|
20131
20175
|
format: MOBILE_UPDATE_REGISTRY_FORMAT,
|
|
20176
|
+
...value.promotionId ? { promotionId: value.promotionId } : {},
|
|
20132
20177
|
promotedAt: value.promotedAt,
|
|
20133
20178
|
...value.releaseId ? { releaseId: value.releaseId } : {},
|
|
20134
20179
|
rollout: value.rollout
|
|
@@ -20151,7 +20196,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20151
20196
|
return true;
|
|
20152
20197
|
const value = createHash15("sha256").update(`${input.appId}\x00${input.channel}\x00${input.releaseId}\x00${input.installationId}`).digest().readUInt32BE(0);
|
|
20153
20198
|
return value / 4294967296 < input.rollout;
|
|
20154
|
-
}, 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, field2) => {
|
|
20199
|
+
}, base64Url = (value) => Buffer.from(value).toString("base64url"), healthPromotionId = (channel) => channel.promotionId ?? digest(new TextEncoder().encode(`${channel.appId}\x00${channel.channel}\x00${channel.releaseId ?? "embedded"}\x00${channel.promotedAt}`)), finiteMetric = (value, field2) => {
|
|
20155
20200
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
20156
20201
|
throw new MobileUpdateRegistryError(`Mobile update health ${field2} is invalid`);
|
|
20157
20202
|
return value;
|
|
@@ -20172,10 +20217,15 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20172
20217
|
const prefix = normalizedPrefix(options.prefix ?? DEFAULT_PREFIX);
|
|
20173
20218
|
const clock = options.clock ?? (() => new Date);
|
|
20174
20219
|
const health = options.health;
|
|
20220
|
+
const rollout = options.rollout;
|
|
20175
20221
|
if (health && health.secret.length < 32)
|
|
20176
20222
|
throw new MobileUpdateRegistryError("Mobile update health secret must contain at least 32 characters");
|
|
20177
20223
|
if (health && !options.store.list)
|
|
20178
20224
|
throw new MobileUpdateRegistryError("Mobile update health requires storage lifecycle listing");
|
|
20225
|
+
if (rollout && (!health || !options.store.list))
|
|
20226
|
+
throw new MobileUpdateRegistryError("Mobile update rollout orchestration requires fleet health and storage lifecycle listing");
|
|
20227
|
+
if (rollout && (rollout.stages.length === 0 || rollout.stages.some((stage, index) => stage.rollout <= 0 || stage.rollout > 1 || !Number.isSafeInteger(stage.minimumReports) || stage.minimumReports < 1 || !Number.isSafeInteger(stage.observationMs) || stage.observationMs < 0 || stage.maximumFailureRate < 0 || stage.maximumFailureRate >= 1 || index > 0 && stage.rollout <= rollout.stages[index - 1].rollout)))
|
|
20228
|
+
throw new MobileUpdateRegistryError("Mobile update rollout stages are invalid");
|
|
20179
20229
|
const minimumReports = health?.autoPause?.minimumReports ?? 20;
|
|
20180
20230
|
const failureThreshold = health?.autoPause?.failureRate ?? 0.2;
|
|
20181
20231
|
if (health && (!Number.isSafeInteger(minimumReports) || minimumReports < 1 || failureThreshold <= 0 || failureThreshold > 1))
|
|
@@ -20188,6 +20238,8 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20188
20238
|
const tombstoneKey = (appId, releaseId) => `${root(appId)}/gc/${releaseId}.json`;
|
|
20189
20239
|
const healthRoot = (appId, promotionId, releaseId) => `${root(appId)}/health/${promotionId}/${releaseId}`;
|
|
20190
20240
|
const pauseKey = (appId, promotionId, releaseId) => `${healthRoot(appId, promotionId, releaseId)}/paused.json`;
|
|
20241
|
+
const rolloutRoot = (appId, promotionId, releaseId) => `${healthRoot(appId, promotionId, releaseId)}/rollout`;
|
|
20242
|
+
const rolloutPlanKey = (appId, promotionId, releaseId) => `${rolloutRoot(appId, promotionId, releaseId)}/plan.json`;
|
|
20191
20243
|
const channelKey = (appId, channel) => {
|
|
20192
20244
|
if (!APP_ID.test(appId) || !NAME.test(channel))
|
|
20193
20245
|
throw new MobileUpdateRegistryError("Mobile update channel identity is invalid");
|
|
@@ -20216,6 +20268,81 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20216
20268
|
throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
|
|
20217
20269
|
return value;
|
|
20218
20270
|
};
|
|
20271
|
+
const listPrefix = async (value) => {
|
|
20272
|
+
const objects = [];
|
|
20273
|
+
const cursors = new Set;
|
|
20274
|
+
let cursor;
|
|
20275
|
+
do {
|
|
20276
|
+
const page = await options.store.list({
|
|
20277
|
+
...cursor ? { cursor } : {},
|
|
20278
|
+
prefix: value
|
|
20279
|
+
});
|
|
20280
|
+
objects.push(...page.objects);
|
|
20281
|
+
if (!page.truncated)
|
|
20282
|
+
break;
|
|
20283
|
+
if (!page.cursor || cursors.has(page.cursor))
|
|
20284
|
+
throw new MobileUpdateRegistryError("Mobile update storage returned an invalid cursor");
|
|
20285
|
+
cursors.add(page.cursor);
|
|
20286
|
+
cursor = page.cursor;
|
|
20287
|
+
} while (true);
|
|
20288
|
+
return objects;
|
|
20289
|
+
};
|
|
20290
|
+
const readVerifiedObject = async (key, label) => {
|
|
20291
|
+
const bytes = await options.store.get(key);
|
|
20292
|
+
if (!bytes)
|
|
20293
|
+
return null;
|
|
20294
|
+
const head = await options.store.head(key);
|
|
20295
|
+
if (!head || head.size !== bytes.byteLength || head.metadata?.sha256 !== digest(bytes))
|
|
20296
|
+
throw new MobileUpdateRegistryError(`Stored mobile update ${label} integrity failed`);
|
|
20297
|
+
return decode2(bytes);
|
|
20298
|
+
};
|
|
20299
|
+
const parseRolloutPlan = (value, promotionId, releaseId) => {
|
|
20300
|
+
if (!object6(value) || value.format !== 1 || value.promotionId !== promotionId || value.releaseId !== releaseId || typeof value.automatic !== "boolean" || !iso(value.createdAt) || !Array.isArray(value.stages))
|
|
20301
|
+
throw new MobileUpdateRegistryError("Stored mobile update rollout plan is invalid");
|
|
20302
|
+
const stages = value.stages.map((stage) => {
|
|
20303
|
+
if (!object6(stage) || typeof stage.rollout !== "number" || typeof stage.maximumFailureRate !== "number" || !Number.isSafeInteger(stage.minimumReports) || !Number.isSafeInteger(stage.observationMs))
|
|
20304
|
+
throw new MobileUpdateRegistryError("Stored mobile update rollout plan is invalid");
|
|
20305
|
+
return {
|
|
20306
|
+
maximumFailureRate: stage.maximumFailureRate,
|
|
20307
|
+
minimumReports: stage.minimumReports,
|
|
20308
|
+
observationMs: stage.observationMs,
|
|
20309
|
+
rollout: stage.rollout
|
|
20310
|
+
};
|
|
20311
|
+
});
|
|
20312
|
+
if (stages.length === 0 || stages.some((stage, index) => stage.rollout <= 0 || stage.rollout > 1 || stage.minimumReports < 1 || stage.observationMs < 0 || stage.maximumFailureRate < 0 || stage.maximumFailureRate >= 1 || index > 0 && stage.rollout <= stages[index - 1].rollout))
|
|
20313
|
+
throw new MobileUpdateRegistryError("Stored mobile update rollout plan is invalid");
|
|
20314
|
+
return {
|
|
20315
|
+
automatic: value.automatic,
|
|
20316
|
+
createdAt: value.createdAt,
|
|
20317
|
+
format: 1,
|
|
20318
|
+
promotionId,
|
|
20319
|
+
releaseId,
|
|
20320
|
+
stages
|
|
20321
|
+
};
|
|
20322
|
+
};
|
|
20323
|
+
const initializeRollout = async (channel, signal) => {
|
|
20324
|
+
if (!rollout || !channel.releaseId)
|
|
20325
|
+
return;
|
|
20326
|
+
if (!rollout.stages.some((stage) => stage.rollout === channel.rollout))
|
|
20327
|
+
throw new MobileUpdateRegistryError("Mobile update promotion rollout must match a configured rollout stage");
|
|
20328
|
+
const promotionId = healthPromotionId(channel);
|
|
20329
|
+
const plan = {
|
|
20330
|
+
automatic: rollout.automatic ?? false,
|
|
20331
|
+
createdAt: channel.promotedAt,
|
|
20332
|
+
format: 1,
|
|
20333
|
+
promotionId,
|
|
20334
|
+
releaseId: channel.releaseId,
|
|
20335
|
+
stages: rollout.stages.map((stage) => ({ ...stage }))
|
|
20336
|
+
};
|
|
20337
|
+
const bytes = json(plan);
|
|
20338
|
+
await options.store.put(rolloutPlanKey(channel.appId, promotionId, channel.releaseId), bytes, {
|
|
20339
|
+
cacheControl: "no-store",
|
|
20340
|
+
contentType: "application/json",
|
|
20341
|
+
maxBytes: bytes.byteLength,
|
|
20342
|
+
metadata: { releaseid: channel.releaseId, sha256: digest(bytes) },
|
|
20343
|
+
signal
|
|
20344
|
+
});
|
|
20345
|
+
};
|
|
20219
20346
|
const signHealthToken = (payload) => {
|
|
20220
20347
|
if (!health)
|
|
20221
20348
|
return null;
|
|
@@ -20254,12 +20381,15 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20254
20381
|
if (await options.store.head(tombstoneKey(appId, releaseId)))
|
|
20255
20382
|
throw new MobileUpdateRegistryError("Mobile update release is marked for collection. Increase retention and apply garbage collection to restore it before promotion");
|
|
20256
20383
|
};
|
|
20257
|
-
const writeChannel = async (input, signal) => {
|
|
20384
|
+
const writeChannel = async (input, signal, beforeWrite) => {
|
|
20385
|
+
const promotedAt = clock().toISOString();
|
|
20258
20386
|
const value = {
|
|
20259
20387
|
...input,
|
|
20260
20388
|
format: MOBILE_UPDATE_REGISTRY_FORMAT,
|
|
20261
|
-
promotedAt
|
|
20389
|
+
promotedAt,
|
|
20390
|
+
promotionId: digest(new TextEncoder().encode(`${input.appId}\x00${input.channel}\x00${input.releaseId ?? "embedded"}\x00${promotedAt}\x00${randomUUID5()}`))
|
|
20262
20391
|
};
|
|
20392
|
+
await beforeWrite?.(value);
|
|
20263
20393
|
const bytes = json(value);
|
|
20264
20394
|
await options.store.put(channelKey(value.appId, value.channel), bytes, {
|
|
20265
20395
|
cacheControl: "no-cache",
|
|
@@ -20274,10 +20404,92 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20274
20404
|
});
|
|
20275
20405
|
return value;
|
|
20276
20406
|
};
|
|
20407
|
+
const rolloutContext = async (channel) => {
|
|
20408
|
+
if (!options.store.list || !channel.releaseId)
|
|
20409
|
+
return null;
|
|
20410
|
+
const promotionId = healthPromotionId(channel);
|
|
20411
|
+
const storedPlan = await readVerifiedObject(rolloutPlanKey(channel.appId, promotionId, channel.releaseId), "rollout plan");
|
|
20412
|
+
if (storedPlan === null)
|
|
20413
|
+
return null;
|
|
20414
|
+
const plan = parseRolloutPlan(storedPlan, promotionId, channel.releaseId);
|
|
20415
|
+
const initialStage = plan.stages.findIndex((stage) => stage.rollout === channel.rollout);
|
|
20416
|
+
if (initialStage < 0)
|
|
20417
|
+
throw new MobileUpdateRegistryError("Stored mobile update rollout does not match its plan");
|
|
20418
|
+
let currentStage = initialStage;
|
|
20419
|
+
let enteredAt = plan.createdAt;
|
|
20420
|
+
const advances = await listPrefix(`${rolloutRoot(channel.appId, promotionId, channel.releaseId)}/advances/`);
|
|
20421
|
+
for (const item of advances) {
|
|
20422
|
+
const value = await readVerifiedObject(item.key, "rollout advancement");
|
|
20423
|
+
if (!object6(value) || value.format !== 1 || value.promotionId !== promotionId || value.releaseId !== channel.releaseId || !Number.isSafeInteger(value.stage) || Number(value.stage) < initialStage || Number(value.stage) >= plan.stages.length || value.rollout !== plan.stages[Number(value.stage)].rollout || !iso(value.createdAt) || typeof value.failureRate !== "number" || !Number.isSafeInteger(value.terminalReports))
|
|
20424
|
+
throw new MobileUpdateRegistryError("Stored mobile update rollout advancement is invalid");
|
|
20425
|
+
if (Number(value.stage) >= currentStage) {
|
|
20426
|
+
currentStage = Number(value.stage);
|
|
20427
|
+
enteredAt = value.createdAt;
|
|
20428
|
+
}
|
|
20429
|
+
}
|
|
20430
|
+
const controls = await listPrefix(`${rolloutRoot(channel.appId, promotionId, channel.releaseId)}/controls/`);
|
|
20431
|
+
const parsedControls = [];
|
|
20432
|
+
for (const item of controls) {
|
|
20433
|
+
const value = await readVerifiedObject(item.key, "rollout control");
|
|
20434
|
+
if (!object6(value) || value.format !== 1 || value.promotionId !== promotionId || value.releaseId !== channel.releaseId || typeof value.id !== "string" || value.action !== "pause" && value.action !== "resume" && value.action !== "cancel" || !iso(value.createdAt) || value.resumedPauseIds !== undefined && (!Array.isArray(value.resumedPauseIds) || value.resumedPauseIds.some((id) => typeof id !== "string")))
|
|
20435
|
+
throw new MobileUpdateRegistryError("Stored mobile update rollout control is invalid");
|
|
20436
|
+
parsedControls.push(value);
|
|
20437
|
+
}
|
|
20438
|
+
const cancelled = parsedControls.some(({ action }) => action === "cancel");
|
|
20439
|
+
const resumedPauseIds = new Set(parsedControls.flatMap((control) => control.resumedPauseIds ?? []));
|
|
20440
|
+
const activePauseIds = parsedControls.filter(({ action, id }) => action === "pause" && !resumedPauseIds.has(id)).map(({ id }) => id);
|
|
20441
|
+
const operatorPaused = activePauseIds.length > 0;
|
|
20442
|
+
const fleetPaused = Boolean(await options.store.head(pauseKey(channel.appId, promotionId, channel.releaseId)));
|
|
20443
|
+
return {
|
|
20444
|
+
cancelled,
|
|
20445
|
+
activePauseIds,
|
|
20446
|
+
channel,
|
|
20447
|
+
currentStage,
|
|
20448
|
+
enteredAt,
|
|
20449
|
+
fleetPaused,
|
|
20450
|
+
operatorPaused,
|
|
20451
|
+
plan,
|
|
20452
|
+
promotionId,
|
|
20453
|
+
rollout: plan.stages[currentStage].rollout
|
|
20454
|
+
};
|
|
20455
|
+
};
|
|
20456
|
+
const writeRolloutControl = async (channel, action, signal) => {
|
|
20457
|
+
const context = await rolloutContext(channel);
|
|
20458
|
+
if (!context)
|
|
20459
|
+
throw new MobileUpdateRegistryError("Mobile update rollout orchestration is not configured");
|
|
20460
|
+
if (context.cancelled)
|
|
20461
|
+
throw new MobileUpdateRegistryError("Mobile update rollout was already cancelled");
|
|
20462
|
+
if (action === "resume" && context.fleetPaused)
|
|
20463
|
+
throw new MobileUpdateRegistryError("A fleet-health pause requires an explicit re-promotion");
|
|
20464
|
+
const releaseId = channel.releaseId;
|
|
20465
|
+
if (!releaseId)
|
|
20466
|
+
throw new MobileUpdateRegistryError("Mobile update channel does not have an active release");
|
|
20467
|
+
const createdAt = clock().toISOString();
|
|
20468
|
+
const id = randomUUID5();
|
|
20469
|
+
const event = {
|
|
20470
|
+
action,
|
|
20471
|
+
createdAt,
|
|
20472
|
+
format: 1,
|
|
20473
|
+
id,
|
|
20474
|
+
promotionId: context.promotionId,
|
|
20475
|
+
releaseId,
|
|
20476
|
+
...action === "resume" ? { resumedPauseIds: context.activePauseIds } : {}
|
|
20477
|
+
};
|
|
20478
|
+
const bytes = json(event);
|
|
20479
|
+
await options.store.put(`${rolloutRoot(channel.appId, context.promotionId, releaseId)}/controls/${createdAt}-${id}-${action}.json`, bytes, {
|
|
20480
|
+
cacheControl: "no-store",
|
|
20481
|
+
contentType: "application/json",
|
|
20482
|
+
maxBytes: bytes.byteLength,
|
|
20483
|
+
metadata: { action, sha256: digest(bytes) },
|
|
20484
|
+
signal
|
|
20485
|
+
});
|
|
20486
|
+
};
|
|
20277
20487
|
const promoteUpdate = async (input) => {
|
|
20278
20488
|
input.signal?.throwIfAborted();
|
|
20279
20489
|
if (input.rollout <= 0 || input.rollout > 1)
|
|
20280
20490
|
throw new MobileUpdateRegistryError("Mobile update rollout is invalid");
|
|
20491
|
+
if (rollout && !rollout.stages.some((stage) => stage.rollout === input.rollout))
|
|
20492
|
+
throw new MobileUpdateRegistryError("Mobile update promotion rollout must match a configured rollout stage");
|
|
20281
20493
|
await assertNotMarked(input.appId, input.releaseId);
|
|
20282
20494
|
const release = await readManifest(input.appId, input.releaseId);
|
|
20283
20495
|
if (!release || release.manifest.channel !== input.channel)
|
|
@@ -20289,7 +20501,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20289
20501
|
...existing?.releaseId && existing.releaseId !== input.releaseId ? { fallbackReleaseId: existing.releaseId } : existing?.fallbackReleaseId ? { fallbackReleaseId: existing.fallbackReleaseId } : {},
|
|
20290
20502
|
releaseId: input.releaseId,
|
|
20291
20503
|
rollout: input.rollout
|
|
20292
|
-
}, input.signal);
|
|
20504
|
+
}, input.signal, (channel) => initializeRollout(channel, input.signal));
|
|
20293
20505
|
return {
|
|
20294
20506
|
appId: input.appId,
|
|
20295
20507
|
channel: input.channel,
|
|
@@ -20302,14 +20514,15 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20302
20514
|
const channel = await readChannel(input.appId, input.channel);
|
|
20303
20515
|
if (!channel?.releaseId)
|
|
20304
20516
|
return { status: "empty" };
|
|
20517
|
+
const rolloutState = await rolloutContext(channel);
|
|
20305
20518
|
let selected = rolloutMember({
|
|
20306
20519
|
appId: input.appId,
|
|
20307
20520
|
channel: input.channel,
|
|
20308
20521
|
installationId: input.installationId,
|
|
20309
20522
|
releaseId: channel.releaseId,
|
|
20310
|
-
rollout: channel.rollout
|
|
20523
|
+
rollout: rolloutState?.rollout ?? channel.rollout
|
|
20311
20524
|
}) ? channel.releaseId : channel.fallbackReleaseId;
|
|
20312
|
-
if (
|
|
20525
|
+
if (selected === channel.releaseId && (rolloutState?.cancelled || rolloutState?.fleetPaused || rolloutState?.operatorPaused || health && await options.store.head(pauseKey(input.appId, healthPromotionId(channel), channel.releaseId))))
|
|
20313
20526
|
selected = channel.fallbackReleaseId;
|
|
20314
20527
|
if (!selected)
|
|
20315
20528
|
return { status: "empty" };
|
|
@@ -20353,23 +20566,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20353
20566
|
if (!channel || !releaseId || channel.releaseId !== releaseId)
|
|
20354
20567
|
return null;
|
|
20355
20568
|
const promotionId = healthPromotionId(channel);
|
|
20356
|
-
const
|
|
20357
|
-
const objects = [];
|
|
20358
|
-
const cursors = new Set;
|
|
20359
|
-
let cursor;
|
|
20360
|
-
do {
|
|
20361
|
-
const page = await options.store.list({
|
|
20362
|
-
...cursor ? { cursor } : {},
|
|
20363
|
-
prefix: prefix2
|
|
20364
|
-
});
|
|
20365
|
-
objects.push(...page.objects);
|
|
20366
|
-
if (!page.truncated)
|
|
20367
|
-
break;
|
|
20368
|
-
if (!page.cursor || cursors.has(page.cursor))
|
|
20369
|
-
throw new MobileUpdateRegistryError("Mobile update health storage returned an invalid cursor");
|
|
20370
|
-
cursors.add(page.cursor);
|
|
20371
|
-
cursor = page.cursor;
|
|
20372
|
-
} while (true);
|
|
20569
|
+
const objects = await listPrefix(`${healthRoot(input.appId, promotionId, releaseId)}/events/`);
|
|
20373
20570
|
const installations = new Set;
|
|
20374
20571
|
const byKind = new Map([...HEALTH_KINDS].map((kind) => [
|
|
20375
20572
|
kind,
|
|
@@ -20408,6 +20605,7 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20408
20605
|
]);
|
|
20409
20606
|
const terminals = new Set([...byKind.get("activated"), ...failures]);
|
|
20410
20607
|
const failureRate = terminals.size === 0 ? 0 : failures.size / terminals.size;
|
|
20608
|
+
const rolloutState = await rolloutContext(channel);
|
|
20411
20609
|
return {
|
|
20412
20610
|
activated: byKind.get("activated").size,
|
|
20413
20611
|
appId: input.appId,
|
|
@@ -20416,17 +20614,107 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20416
20614
|
downloadFailed: byKind.get("download-failed").size,
|
|
20417
20615
|
failureRate,
|
|
20418
20616
|
failures: failures.size,
|
|
20419
|
-
paused: Boolean(await options.store.head(pauseKey(input.appId, promotionId, releaseId))),
|
|
20617
|
+
paused: Boolean(rolloutState?.cancelled || rolloutState?.fleetPaused || rolloutState?.operatorPaused || await options.store.head(pauseKey(input.appId, promotionId, releaseId))),
|
|
20420
20618
|
promotionId,
|
|
20421
20619
|
quarantined: byKind.get("quarantined").size,
|
|
20422
20620
|
releaseId,
|
|
20423
20621
|
reportedInstallations: installations.size,
|
|
20424
20622
|
rolledBack: byKind.get("rolled-back").size,
|
|
20425
|
-
rollout: channel.rollout,
|
|
20623
|
+
rollout: rolloutState?.rollout ?? channel.rollout,
|
|
20426
20624
|
terminalReports: terminals.size,
|
|
20427
20625
|
transfer
|
|
20428
20626
|
};
|
|
20429
20627
|
};
|
|
20628
|
+
const inspectUpdateRollout = async (input) => {
|
|
20629
|
+
const channel = await readChannel(input.appId, input.channel);
|
|
20630
|
+
if (!channel?.releaseId)
|
|
20631
|
+
return null;
|
|
20632
|
+
const context = await rolloutContext(channel);
|
|
20633
|
+
if (!context)
|
|
20634
|
+
throw new MobileUpdateRegistryError("Mobile update rollout orchestration is not configured");
|
|
20635
|
+
const healthReport = await inspectUpdateHealth(input);
|
|
20636
|
+
if (!healthReport)
|
|
20637
|
+
return null;
|
|
20638
|
+
const paused = context.fleetPaused || context.operatorPaused;
|
|
20639
|
+
const complete = context.currentStage === context.plan.stages.length - 1;
|
|
20640
|
+
return {
|
|
20641
|
+
...healthReport,
|
|
20642
|
+
automatic: context.plan.automatic,
|
|
20643
|
+
currentStage: context.currentStage,
|
|
20644
|
+
enteredAt: context.enteredAt,
|
|
20645
|
+
...!complete ? { nextStage: context.plan.stages[context.currentStage + 1] } : {},
|
|
20646
|
+
...context.fleetPaused ? { pausedBy: "fleet-health" } : context.operatorPaused ? { pausedBy: "operator" } : {},
|
|
20647
|
+
status: context.cancelled ? "cancelled" : paused ? "paused" : complete ? "complete" : "active"
|
|
20648
|
+
};
|
|
20649
|
+
};
|
|
20650
|
+
const advanceRollout = async (input, strict) => {
|
|
20651
|
+
input.signal?.throwIfAborted();
|
|
20652
|
+
const channel = await readChannel(input.appId, input.channel);
|
|
20653
|
+
if (!channel?.releaseId)
|
|
20654
|
+
throw new MobileUpdateRegistryError("Mobile update channel does not have an active release");
|
|
20655
|
+
const context = await rolloutContext(channel);
|
|
20656
|
+
if (!context)
|
|
20657
|
+
throw new MobileUpdateRegistryError("Mobile update rollout orchestration is not configured");
|
|
20658
|
+
const report = await inspectUpdateRollout(input);
|
|
20659
|
+
if (!report)
|
|
20660
|
+
throw new MobileUpdateRegistryError("Mobile update rollout report is unavailable");
|
|
20661
|
+
const nextStage = context.plan.stages[context.currentStage + 1];
|
|
20662
|
+
if (!nextStage)
|
|
20663
|
+
return report;
|
|
20664
|
+
if (input.rollout !== undefined && input.rollout !== nextStage.rollout)
|
|
20665
|
+
throw new MobileUpdateRegistryError("Mobile update rollout can advance only to the next configured stage");
|
|
20666
|
+
if (report.status !== "active") {
|
|
20667
|
+
if (strict)
|
|
20668
|
+
throw new MobileUpdateRegistryError(`Mobile update rollout cannot advance while ${report.status}`);
|
|
20669
|
+
return report;
|
|
20670
|
+
}
|
|
20671
|
+
const gate = context.plan.stages[context.currentStage];
|
|
20672
|
+
const observedMs = clock().getTime() - Date.parse(context.enteredAt);
|
|
20673
|
+
const blocked = report.terminalReports < gate.minimumReports || report.failureRate > gate.maximumFailureRate || observedMs < gate.observationMs;
|
|
20674
|
+
if (blocked) {
|
|
20675
|
+
if (strict)
|
|
20676
|
+
throw new MobileUpdateRegistryError(`Mobile update rollout needs ${gate.minimumReports} terminal reports, at most ${(gate.maximumFailureRate * 100).toFixed(1)}% failures, and ${gate.observationMs}ms observation at the current stage`);
|
|
20677
|
+
return report;
|
|
20678
|
+
}
|
|
20679
|
+
const stage = context.currentStage + 1;
|
|
20680
|
+
const event = {
|
|
20681
|
+
createdAt: clock().toISOString(),
|
|
20682
|
+
failureRate: report.failureRate,
|
|
20683
|
+
format: 1,
|
|
20684
|
+
promotionId: context.promotionId,
|
|
20685
|
+
releaseId: channel.releaseId,
|
|
20686
|
+
rollout: nextStage.rollout,
|
|
20687
|
+
stage,
|
|
20688
|
+
terminalReports: report.terminalReports
|
|
20689
|
+
};
|
|
20690
|
+
const bytes = json(event);
|
|
20691
|
+
await options.store.put(`${rolloutRoot(input.appId, context.promotionId, channel.releaseId)}/advances/${String(stage).padStart(4, "0")}.json`, bytes, {
|
|
20692
|
+
cacheControl: "no-store",
|
|
20693
|
+
contentType: "application/json",
|
|
20694
|
+
maxBytes: bytes.byteLength,
|
|
20695
|
+
metadata: { releaseid: channel.releaseId, sha256: digest(bytes) },
|
|
20696
|
+
signal: input.signal
|
|
20697
|
+
});
|
|
20698
|
+
return await inspectUpdateRollout(input);
|
|
20699
|
+
};
|
|
20700
|
+
const advanceUpdateRollout = (input) => advanceRollout(input, true);
|
|
20701
|
+
const reconcileUpdateRollout = async (input) => {
|
|
20702
|
+
const report = await inspectUpdateRollout(input);
|
|
20703
|
+
if (!report || !report.automatic)
|
|
20704
|
+
return report;
|
|
20705
|
+
return advanceRollout(input, false);
|
|
20706
|
+
};
|
|
20707
|
+
const rolloutControl = (action) => async (input) => {
|
|
20708
|
+
input.signal?.throwIfAborted();
|
|
20709
|
+
const channel = await readChannel(input.appId, input.channel);
|
|
20710
|
+
if (!channel?.releaseId)
|
|
20711
|
+
throw new MobileUpdateRegistryError("Mobile update channel does not have an active release");
|
|
20712
|
+
await writeRolloutControl(channel, action, input.signal);
|
|
20713
|
+
return await inspectUpdateRollout(input);
|
|
20714
|
+
};
|
|
20715
|
+
const pauseUpdateRollout = rolloutControl("pause");
|
|
20716
|
+
const resumeUpdateRollout = rolloutControl("resume");
|
|
20717
|
+
const cancelUpdateRollout = rolloutControl("cancel");
|
|
20430
20718
|
const recordUpdateHealth = async (input) => {
|
|
20431
20719
|
const payload = verifyHealthToken(input.token);
|
|
20432
20720
|
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")
|
|
@@ -20481,6 +20769,11 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20481
20769
|
});
|
|
20482
20770
|
report = { ...report, paused: true };
|
|
20483
20771
|
}
|
|
20772
|
+
if (rollout && (input.kind === "activated" || FAILURE_HEALTH_KINDS.has(input.kind)))
|
|
20773
|
+
return await reconcileUpdateRollout({
|
|
20774
|
+
appId: input.appId,
|
|
20775
|
+
channel: input.channel
|
|
20776
|
+
}) ?? report;
|
|
20484
20777
|
return report;
|
|
20485
20778
|
};
|
|
20486
20779
|
const retentionValues = (input) => {
|
|
@@ -20726,6 +21019,14 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
|
|
|
20726
21019
|
};
|
|
20727
21020
|
return {
|
|
20728
21021
|
...health ? { inspectUpdateHealth, issueUpdateHealthToken, recordUpdateHealth } : {},
|
|
21022
|
+
...rollout ? {
|
|
21023
|
+
advanceUpdateRollout,
|
|
21024
|
+
cancelUpdateRollout,
|
|
21025
|
+
inspectUpdateRollout,
|
|
21026
|
+
pauseUpdateRollout,
|
|
21027
|
+
reconcileUpdateRollout,
|
|
21028
|
+
resumeUpdateRollout
|
|
21029
|
+
} : {},
|
|
20729
21030
|
inspectUpdateStorage: async (input) => (await inventory(input)).report,
|
|
20730
21031
|
pruneUpdates,
|
|
20731
21032
|
publishUpdate: async (input) => {
|
|
@@ -21339,6 +21640,11 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
|
|
|
21339
21640
|
return;
|
|
21340
21641
|
if (typeof module.registry.inspectUpdateHealth !== "function" || typeof module.registry.issueUpdateHealthToken !== "function" || typeof module.registry.recordUpdateHealth !== "function")
|
|
21341
21642
|
throw new TypeError("Mobile update fleet health is enabled but the registry is not provisioned for it. Run `absolute mobile update provision --force`.");
|
|
21643
|
+
}, verifyRolloutModule = (config, module) => {
|
|
21644
|
+
if (!config.updateServer?.rollout)
|
|
21645
|
+
return;
|
|
21646
|
+
if (typeof module.registry.advanceUpdateRollout !== "function" || typeof module.registry.cancelUpdateRollout !== "function" || typeof module.registry.inspectUpdateRollout !== "function" || typeof module.registry.pauseUpdateRollout !== "function" || typeof module.registry.reconcileUpdateRollout !== "function" || typeof module.registry.resumeUpdateRollout !== "function")
|
|
21647
|
+
throw new TypeError("Mobile update rollout orchestration is enabled but the registry is not provisioned for it. Run `absolute mobile update provision --force`.");
|
|
21342
21648
|
}, expoSigningOptions = (config) => {
|
|
21343
21649
|
if (!config.updates?.expoCodeSigning)
|
|
21344
21650
|
return;
|
|
@@ -21373,6 +21679,7 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
|
|
|
21373
21679
|
if (options.production) {
|
|
21374
21680
|
await verifyDurableModule(module);
|
|
21375
21681
|
verifyHealthModule(config, module);
|
|
21682
|
+
verifyRolloutModule(config, module);
|
|
21376
21683
|
}
|
|
21377
21684
|
const manifest = new URL(updates.manifestUrl);
|
|
21378
21685
|
if (!manifest.pathname.endsWith("/update.json"))
|
|
@@ -21393,6 +21700,7 @@ var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MOD
|
|
|
21393
21700
|
const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, config.updateServer?.registryModule);
|
|
21394
21701
|
await verifyDurableModule(module);
|
|
21395
21702
|
verifyHealthModule(config, module);
|
|
21703
|
+
verifyRolloutModule(config, module);
|
|
21396
21704
|
if (config.engine === "expo")
|
|
21397
21705
|
expoSigningOptions(config);
|
|
21398
21706
|
return module.metadata;
|
|
@@ -21464,17 +21772,25 @@ export default createMobileUpdateRegistry({
|
|
|
21464
21772
|
`;
|
|
21465
21773
|
}, renderAbsoluteMobileUpdateRegistry = (options) => {
|
|
21466
21774
|
const source = renderAbsoluteMobileUpdateRegistryBase(options);
|
|
21467
|
-
|
|
21468
|
-
|
|
21469
|
-
|
|
21470
|
-
|
|
21775
|
+
let generated = "";
|
|
21776
|
+
if (options.health) {
|
|
21777
|
+
const secret = options.storage === "local" ? `process.env.${options.health.secretEnv} ?? 'absolutejs-local-health-secret-not-for-production'` : `required('${options.health.secretEnv}')`;
|
|
21778
|
+
generated += ` health: {
|
|
21471
21779
|
autoPause: { failureRate: ${options.health.failureRate}, minimumReports: ${options.health.minimumReports} },
|
|
21472
21780
|
secret: ${secret}
|
|
21473
21781
|
},
|
|
21474
21782
|
`;
|
|
21783
|
+
}
|
|
21784
|
+
if (options.rollout)
|
|
21785
|
+
generated += ` rollout: ${JSON.stringify(options.rollout, null, "\t").replaceAll(`
|
|
21786
|
+
`, `
|
|
21787
|
+
`)},
|
|
21788
|
+
`;
|
|
21789
|
+
if (!generated)
|
|
21790
|
+
return source;
|
|
21475
21791
|
return source.replace(`export default createMobileUpdateRegistry({
|
|
21476
21792
|
`, `export default createMobileUpdateRegistry({
|
|
21477
|
-
${
|
|
21793
|
+
${generated}`);
|
|
21478
21794
|
}, writeAbsoluteMobileUpdateRegistry = async (options) => {
|
|
21479
21795
|
const path2 = projectPath3(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
|
|
21480
21796
|
if (!options.force) {
|
|
@@ -21487,6 +21803,7 @@ ${health}`);
|
|
|
21487
21803
|
await mkdir18(dirname19(path2), { recursive: true });
|
|
21488
21804
|
await Bun.write(path2, renderAbsoluteMobileUpdateRegistry({
|
|
21489
21805
|
...options.health ? { health: options.health } : {},
|
|
21806
|
+
...options.rollout ? { rollout: options.rollout } : {},
|
|
21490
21807
|
publicKeys: options.publicKeys,
|
|
21491
21808
|
storage: options.storage
|
|
21492
21809
|
}));
|
|
@@ -39554,14 +39871,7 @@ var lifecycleMethod = (publisher, name) => {
|
|
|
39554
39871
|
throw new TypeError(`Mobile update registry does not support ${name}. Re-run \`absolute mobile update provision --force\` after upgrading @absolutejs/deploy.`);
|
|
39555
39872
|
return method;
|
|
39556
39873
|
};
|
|
39557
|
-
var
|
|
39558
|
-
const report = await lifecycleMethod(options.publisher, "inspectUpdateHealth")({
|
|
39559
|
-
appId: options.appId,
|
|
39560
|
-
channel: options.channel,
|
|
39561
|
-
...options.releaseId ? { releaseId: options.releaseId } : {}
|
|
39562
|
-
});
|
|
39563
|
-
if (report === null)
|
|
39564
|
-
return null;
|
|
39874
|
+
var validateHealthReport = (report, options) => {
|
|
39565
39875
|
if (!object5(report) || report.appId !== options.appId || report.channel !== options.channel || options.releaseId !== undefined && report.releaseId !== options.releaseId || typeof report.paused !== "boolean" || ![
|
|
39566
39876
|
report.activated,
|
|
39567
39877
|
report.downloaded,
|
|
@@ -39582,6 +39892,45 @@ var inspectAbsoluteMobileUpdateHealth = async (options) => {
|
|
|
39582
39892
|
throw new TypeError("Mobile update registry returned an invalid health report.");
|
|
39583
39893
|
return report;
|
|
39584
39894
|
};
|
|
39895
|
+
var inspectAbsoluteMobileUpdateHealth = async (options) => {
|
|
39896
|
+
const report = await lifecycleMethod(options.publisher, "inspectUpdateHealth")({
|
|
39897
|
+
appId: options.appId,
|
|
39898
|
+
channel: options.channel,
|
|
39899
|
+
...options.releaseId ? { releaseId: options.releaseId } : {}
|
|
39900
|
+
});
|
|
39901
|
+
if (report === null)
|
|
39902
|
+
return null;
|
|
39903
|
+
return validateHealthReport(report, options);
|
|
39904
|
+
};
|
|
39905
|
+
var validateRolloutReport = (report, options) => {
|
|
39906
|
+
validateHealthReport(report, options);
|
|
39907
|
+
const validStage = (stage) => stage === undefined || object5(stage) && Number.isFinite(stage.rollout) && stage.rollout > 0 && stage.rollout <= 1 && Number.isSafeInteger(stage.minimumReports) && stage.minimumReports > 0 && Number.isSafeInteger(stage.observationMs) && stage.observationMs >= 0 && Number.isFinite(stage.maximumFailureRate) && stage.maximumFailureRate >= 0 && stage.maximumFailureRate < 1;
|
|
39908
|
+
if (typeof report.automatic !== "boolean" || !Number.isSafeInteger(report.currentStage) || report.currentStage < 0 || !Number.isFinite(Date.parse(report.enteredAt)) || !["active", "cancelled", "complete", "paused"].includes(report.status) || report.pausedBy !== undefined && report.pausedBy !== "fleet-health" && report.pausedBy !== "operator" || !validStage(report.nextStage) || report.paused !== (report.status === "paused" || report.status === "cancelled"))
|
|
39909
|
+
throw new TypeError("Mobile update registry returned an invalid rollout report.");
|
|
39910
|
+
return report;
|
|
39911
|
+
};
|
|
39912
|
+
var inspectAbsoluteMobileUpdateRollout = async (options) => {
|
|
39913
|
+
const report = await lifecycleMethod(options.publisher, "inspectUpdateRollout")({ appId: options.appId, channel: options.channel });
|
|
39914
|
+
return report === null ? null : validateRolloutReport(report, options);
|
|
39915
|
+
};
|
|
39916
|
+
var mutateAbsoluteMobileUpdateRollout = async (method, options) => validateRolloutReport(await lifecycleMethod(options.publisher, method)({
|
|
39917
|
+
appId: options.appId,
|
|
39918
|
+
channel: options.channel,
|
|
39919
|
+
...method === "advanceUpdateRollout" && options.rollout !== undefined ? { rollout: options.rollout } : {},
|
|
39920
|
+
...options.signal ? { signal: options.signal } : {}
|
|
39921
|
+
}), options);
|
|
39922
|
+
var advanceAbsoluteMobileUpdateRollout = (options) => mutateAbsoluteMobileUpdateRollout("advanceUpdateRollout", options);
|
|
39923
|
+
var cancelAbsoluteMobileUpdateRollout = (options) => mutateAbsoluteMobileUpdateRollout("cancelUpdateRollout", options);
|
|
39924
|
+
var pauseAbsoluteMobileUpdateRollout = (options) => mutateAbsoluteMobileUpdateRollout("pauseUpdateRollout", options);
|
|
39925
|
+
var reconcileAbsoluteMobileUpdateRollout = async (options) => {
|
|
39926
|
+
const report = await lifecycleMethod(options.publisher, "reconcileUpdateRollout")({
|
|
39927
|
+
appId: options.appId,
|
|
39928
|
+
channel: options.channel,
|
|
39929
|
+
...options.signal ? { signal: options.signal } : {}
|
|
39930
|
+
});
|
|
39931
|
+
return report === null ? null : validateRolloutReport(report, options);
|
|
39932
|
+
};
|
|
39933
|
+
var resumeAbsoluteMobileUpdateRollout = (options) => mutateAbsoluteMobileUpdateRollout("resumeUpdateRollout", options);
|
|
39585
39934
|
var validOptionalCounters = (values) => values.every((value) => value === undefined) || values.every((value) => Number.isSafeInteger(value) && (value ?? -1) >= 0);
|
|
39586
39935
|
var validateStorageIdentity = (result, appId) => {
|
|
39587
39936
|
if (!object5(result) || result.appId !== appId || !Array.isArray(result.releases) || ![
|
|
@@ -39782,6 +40131,7 @@ export {
|
|
|
39782
40131
|
acceptsAbsoluteRouteData,
|
|
39783
40132
|
acquireAbsoluteRemoteMacReleaseLease,
|
|
39784
40133
|
activateAbsoluteMobilePage,
|
|
40134
|
+
advanceAbsoluteMobileUpdateRollout,
|
|
39785
40135
|
applyAbsoluteNativeDeepLinks,
|
|
39786
40136
|
applyAbsoluteNativeDeviceCapabilities,
|
|
39787
40137
|
applyAbsoluteNativeObservability,
|
|
@@ -39793,6 +40143,7 @@ export {
|
|
|
39793
40143
|
buildAbsoluteMobileCompatibilityRelease,
|
|
39794
40144
|
buildAbsoluteMobileUpdate,
|
|
39795
40145
|
buildAbsoluteRemoteIosRelease,
|
|
40146
|
+
cancelAbsoluteMobileUpdateRollout,
|
|
39796
40147
|
canonicalizeAbsoluteMobileUpdate,
|
|
39797
40148
|
captureAbsoluteMobileRouteGraph,
|
|
39798
40149
|
captureAbsoluteRemoteMacCommand,
|
|
@@ -39844,6 +40195,7 @@ export {
|
|
|
39844
40195
|
inspectAbsoluteAndroidInstalledApp,
|
|
39845
40196
|
inspectAbsoluteMobileRouteMetadata,
|
|
39846
40197
|
inspectAbsoluteMobileUpdateHealth,
|
|
40198
|
+
inspectAbsoluteMobileUpdateRollout,
|
|
39847
40199
|
inspectAbsoluteMobileUpdateServer,
|
|
39848
40200
|
inspectAbsoluteMobileUpdateStorage,
|
|
39849
40201
|
inspectAbsoluteRemoteMac,
|
|
@@ -39895,6 +40247,7 @@ export {
|
|
|
39895
40247
|
parseIosDeviceTypes,
|
|
39896
40248
|
parseIosRuntimes,
|
|
39897
40249
|
parseIosSimulators,
|
|
40250
|
+
pauseAbsoluteMobileUpdateRollout,
|
|
39898
40251
|
planAbsoluteExpoDevSession,
|
|
39899
40252
|
prepareAbsoluteAndroidRelease,
|
|
39900
40253
|
prepareAbsoluteIosDevProject,
|
|
@@ -39910,6 +40263,7 @@ export {
|
|
|
39910
40263
|
readAbsoluteMobileLinkIntent,
|
|
39911
40264
|
readAbsoluteMobileMaterializedReleases,
|
|
39912
40265
|
readAbsoluteMobileUpdate,
|
|
40266
|
+
reconcileAbsoluteMobileUpdateRollout,
|
|
39913
40267
|
redactAbsoluteIosLog,
|
|
39914
40268
|
removeAbsoluteRemoteMacProfile,
|
|
39915
40269
|
renderAbsoluteMobileUpdateRegistry,
|
|
@@ -39925,6 +40279,7 @@ export {
|
|
|
39925
40279
|
resolveAbsoluteMobileNavigation,
|
|
39926
40280
|
resolveAbsoluteMobileRoute,
|
|
39927
40281
|
resolveAbsoluteMobileUpdateRuntime,
|
|
40282
|
+
resumeAbsoluteMobileUpdateRollout,
|
|
39928
40283
|
retainAbsoluteMobileCompatibilityArtifacts,
|
|
39929
40284
|
rollbackAbsoluteMobileUpdate,
|
|
39930
40285
|
runAbsoluteAndroidUpgradeConformance,
|
|
@@ -39953,5 +40308,5 @@ export {
|
|
|
39953
40308
|
writeAbsoluteMobileUpdateRegistry
|
|
39954
40309
|
};
|
|
39955
40310
|
|
|
39956
|
-
//# debugId=
|
|
40311
|
+
//# debugId=DB548297ABB3C7C964756E2164756E21
|
|
39957
40312
|
//# sourceMappingURL=index.js.map
|