@absolutejs/absolute 0.20.0-beta.86 → 0.20.0-beta.88

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.
@@ -12,7 +12,7 @@ import {
12
12
  createIslandRegistryDefinitionPlugin,
13
13
  finalizeAbsoluteMobileCompatibilityBuild,
14
14
  resolveServerBundleExternals
15
- } from "./index-x48brywr.js";
15
+ } from "./index-jqdt7841.js";
16
16
  import {
17
17
  loadIslandRegistryBuildInfo
18
18
  } from "./index-bzs19w3r.js";
@@ -35,7 +35,7 @@ import {
35
35
  import"./index-w5vswatm.js";
36
36
  import {
37
37
  normalizeAbsoluteMobileConfig
38
- } from "./index-3tsmbyze.js";
38
+ } from "./index-rd2hbttb.js";
39
39
  import"./index-tame6e2c.js";
40
40
  import {
41
41
  ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  normalizeAbsoluteMobileConfig
4
- } from "./index-3tsmbyze.js";
4
+ } from "./index-rd2hbttb.js";
5
5
  import"./index-mywsk201.js";
6
6
  export {
7
7
  normalizeAbsoluteMobileConfig
@@ -1281,7 +1281,7 @@ var loadAbsoluteMobileDevModules = async () => {
1281
1281
  remoteMacProtocol,
1282
1282
  iosNativeWatcher
1283
1283
  ] = await Promise.all([
1284
- import("./config-z2kf085h.js"),
1284
+ import("./config-vy04mqce.js"),
1285
1285
  import("./expoProject-dac8fqhq.js"),
1286
1286
  import("./expoDevController-n19kb5fr.js"),
1287
1287
  import("./nativeAuth-zx4tsb1f.js"),
@@ -6,7 +6,7 @@ import {
6
6
  createIslandRegistryDefinitionPlugin,
7
7
  finalizeAbsoluteMobileCompatibilityBuild,
8
8
  resolveServerBundleExternals
9
- } from "./index-x48brywr.js";
9
+ } from "./index-jqdt7841.js";
10
10
  import {
11
11
  loadIslandRegistryBuildInfo
12
12
  } from "./index-bzs19w3r.js";
@@ -26,7 +26,7 @@ import {
26
26
  } from "./index-9kwmb3c0.js";
27
27
  import {
28
28
  normalizeAbsoluteMobileConfig
29
- } from "./index-3tsmbyze.js";
29
+ } from "./index-rd2hbttb.js";
30
30
  import {
31
31
  installAbsoluteMobileAuthEnvironment
32
32
  } from "./index-ar4gz15x.js";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-s2tazax7.js";
5
5
  import {
6
6
  normalizeAbsoluteMobileConfig
7
- } from "./index-3tsmbyze.js";
7
+ } from "./index-rd2hbttb.js";
8
8
  import {
9
9
  assertAbsoluteDeviceCapabilityPackages,
10
10
  resolveAbsoluteMobileUpdateRuntime,
@@ -13,6 +13,14 @@ var ENVIRONMENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
13
13
  var DEFAULT_UPDATE_BOOT_TIMEOUT_MS = 20000;
14
14
  var DEFAULT_UPDATE_HEALTH_FAILURE_RATE = 0.2;
15
15
  var DEFAULT_UPDATE_HEALTH_MINIMUM_REPORTS = 20;
16
+ var DEFAULT_UPDATE_ROLLOUT_FAILURE_RATE = 0.05;
17
+ var DEFAULT_UPDATE_ROLLOUT_OBSERVATION_MINUTES = 60;
18
+ var MINUTE_MS = 60 * 1000;
19
+ var DEFAULT_UPDATE_ROLLOUT_STAGES = [
20
+ { minimumReports: 20, observationMinutes: 60, rollout: 0.05 },
21
+ { minimumReports: 100, observationMinutes: 360, rollout: 0.25 },
22
+ { minimumReports: 100, observationMinutes: 0, rollout: 1 }
23
+ ];
16
24
  var MINIMUM_UPDATE_BOOT_TIMEOUT_MS = 5000;
17
25
  var MAXIMUM_UPDATE_BOOT_TIMEOUT_MS = 120000;
18
26
  var 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])?))*$/;
@@ -246,6 +254,42 @@ var normalizeUpdateServer = (config, productionOrigin, projectRoot, updates) =>
246
254
  throw new TypeError("mobile.updates.server.health.secretEnv must be a valid environment variable name.");
247
255
  health = { failureRate, minimumReports, secretEnv };
248
256
  }
257
+ const configuredRollout = config.updates.server?.rollout;
258
+ let rollout;
259
+ if (configuredRollout !== undefined && configuredRollout !== false) {
260
+ if (!health)
261
+ throw new TypeError("mobile.updates.server.rollout requires fleet health to be enabled.");
262
+ if (configuredRollout.automatic !== undefined && typeof configuredRollout.automatic !== "boolean")
263
+ throw new TypeError("mobile.updates.server.rollout.automatic must be boolean.");
264
+ const configuredStages = configuredRollout.stages ?? DEFAULT_UPDATE_ROLLOUT_STAGES;
265
+ const stages = configuredStages.map((stage, index) => {
266
+ const previousStage = configuredStages[index - 1];
267
+ const maximumFailureRate = stage.maximumFailureRate ?? DEFAULT_UPDATE_ROLLOUT_FAILURE_RATE;
268
+ const minimumReports = stage.minimumReports ?? DEFAULT_UPDATE_HEALTH_MINIMUM_REPORTS;
269
+ const observationMinutes = stage.observationMinutes ?? DEFAULT_UPDATE_ROLLOUT_OBSERVATION_MINUTES;
270
+ const observationMs = observationMinutes * MINUTE_MS;
271
+ if (!Number.isFinite(stage.rollout) || stage.rollout <= 0 || stage.rollout > 1 || previousStage !== undefined && stage.rollout <= previousStage.rollout)
272
+ throw new TypeError("mobile.updates.server.rollout stages must be strictly increasing fractions greater than 0 and at most 1.");
273
+ if (!Number.isFinite(maximumFailureRate) || maximumFailureRate < 0 || maximumFailureRate >= health.failureRate)
274
+ throw new TypeError("mobile.updates.server.rollout maximumFailureRate must be non-negative and lower than the fleet-health pause rate.");
275
+ if (!Number.isSafeInteger(minimumReports) || minimumReports < health.minimumReports)
276
+ throw new TypeError("mobile.updates.server.rollout minimumReports must be an integer at least as large as the fleet-health minimumReports.");
277
+ if (!Number.isFinite(observationMinutes) || observationMinutes < 0 || !Number.isSafeInteger(observationMs))
278
+ throw new TypeError("mobile.updates.server.rollout observationMinutes must produce a non-negative whole number of milliseconds.");
279
+ return {
280
+ maximumFailureRate,
281
+ minimumReports,
282
+ observationMs,
283
+ rollout: stage.rollout
284
+ };
285
+ });
286
+ if (stages.length === 0 || stages.at(-1)?.rollout !== 1)
287
+ throw new TypeError("mobile.updates.server.rollout stages must end at rollout 1.");
288
+ rollout = {
289
+ automatic: configuredRollout.automatic ?? false,
290
+ stages
291
+ };
292
+ }
249
293
  if (autoMount) {
250
294
  const manifest = new URL(updates.manifestUrl);
251
295
  if (manifest.origin !== productionOrigin)
@@ -282,6 +326,7 @@ var normalizeUpdateServer = (config, productionOrigin, projectRoot, updates) =>
282
326
  autoMount,
283
327
  expoCodeSigningKeys,
284
328
  ...health ? { health } : {},
329
+ ...rollout ? { rollout } : {},
285
330
  registryModule
286
331
  };
287
332
  };
package/dist/cli/index.js CHANGED
@@ -58,7 +58,7 @@ if (command === "dev") {
58
58
  }
59
59
  const positionalArgs = stripNamedArgs("--config", "--android-device", "--ios-device").filter((arg) => arg !== "--no-mobile" && arg !== "--eager");
60
60
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
61
- const { dev } = await import("./dev-3hweza3s.js");
61
+ const { dev } = await import("./dev-6shm8qsn.js");
62
62
  await dev(serverEntry, configPath, {
63
63
  androidDevice,
64
64
  eager: args.includes("--eager"),
@@ -71,7 +71,7 @@ if (command === "dev") {
71
71
  const configPath = parseNamedArg("--config");
72
72
  const positionalArgs = stripNamedArgs("--outdir", "--config").filter((arg) => arg !== "--prebuilt");
73
73
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
74
- const { start } = await import("./start-7t186mhg.js");
74
+ const { start } = await import("./start-7jg0csqy.js");
75
75
  await start(serverEntry, outdir, configPath, {
76
76
  prebuilt: args.includes("--prebuilt")
77
77
  });
@@ -81,7 +81,7 @@ if (command === "dev") {
81
81
  const configPath = parseNamedArg("--config");
82
82
  const positionalArgs = stripNamedArgs("--outdir", "--config");
83
83
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
84
- const { start } = await import("./start-7t186mhg.js");
84
+ const { start } = await import("./start-7jg0csqy.js");
85
85
  await start(serverEntry, outdir, configPath, { prepareOnly: true });
86
86
  } else if (command === "build") {
87
87
  sendTelemetryEvent("cli:command", { command });
@@ -197,13 +197,13 @@ if (command === "dev") {
197
197
  const configPath = parseNamedArg("--config");
198
198
  const positionalArgs = stripNamedArgs("--outdir", "--outfile", "--config");
199
199
  const serverEntry = positionalArgs[0] ?? DEFAULT_SERVER_ENTRY;
200
- const { compile } = await import("./compile-e9cn3xpx.js");
200
+ const { compile } = await import("./compile-5hchbcbd.js");
201
201
  await compile(serverEntry, outdir, outfile, configPath);
202
202
  } else if (command === "mobile") {
203
203
  sendTelemetryEvent("cli:command", {
204
204
  command: `mobile:${workspaceCommand ?? "unknown"}`
205
205
  });
206
- const { runMobile } = await import("./mobile-2p3z33t1.js");
206
+ const { runMobile } = await import("./mobile-bg1jy34n.js");
207
207
  try {
208
208
  await runMobile(args);
209
209
  } catch (error) {
@@ -7,12 +7,12 @@ import {
7
7
  } from "./index-9r7n9dqp.js";
8
8
  import {
9
9
  start
10
- } from "./index-9rk7v35t.js";
10
+ } from "./index-39j0dhq7.js";
11
11
  import {
12
12
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
13
13
  ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
14
14
  resolveAbsoluteMobileRoute
15
- } from "./index-x48brywr.js";
15
+ } from "./index-jqdt7841.js";
16
16
  import"./index-bzs19w3r.js";
17
17
  import"./index-s2tazax7.js";
18
18
  import {
@@ -52,7 +52,7 @@ import {
52
52
  import"./index-w5vswatm.js";
53
53
  import {
54
54
  normalizeAbsoluteMobileConfig
55
- } from "./index-3tsmbyze.js";
55
+ } from "./index-rd2hbttb.js";
56
56
  import {
57
57
  ABSOLUTE_MOBILE_UPDATE_FORMAT,
58
58
  absoluteDeviceNativeRequirements,
@@ -17519,9 +17519,12 @@ public final class AbsoluteMobileUpdateWatchdogPlugin: CAPPlugin, CAPBridgedPlug
17519
17519
  private static func recover(_ reason: String) -> (String, String?, Bool)? {
17520
17520
  var value = state()
17521
17521
  guard let release = value["pendingRelease"] as? String, validRelease(release) else { return nil }
17522
- let previous = value["previousPath"] as? String
17523
17522
  let active = value["activeRelease"] as? String
17524
- let hasActive = active.map(validRelease) ?? false
17523
+ let activePath = active.flatMap { candidate in
17524
+ validRelease(candidate) ? snapshotRoot()?.appendingPathComponent(candidate, isDirectory: true).path : nil
17525
+ }
17526
+ let hasActive = activePath.map { FileManager.default.fileExists(atPath: $0) } ?? false
17527
+ let previous = hasActive ? activePath : nil
17525
17528
  let started = value["pendingStartedAt"] as? Double ?? Date().timeIntervalSince1970 * 1000
17526
17529
  let duration = max(0, Date().timeIntervalSince1970 * 1000 - started)
17527
17530
  value.removeValue(forKey: "pendingRelease")
@@ -17693,8 +17696,10 @@ public final class AbsoluteMobileUpdateWatchdogPlugin extends Plugin {
17693
17696
  JSONObject value = state(context);
17694
17697
  String release = value.optString("pendingRelease", "");
17695
17698
  if (!validRelease(release)) return null;
17696
- String previous = value.optString("previousPath", "");
17697
- boolean hasActive = validRelease(value.optString("activeRelease", ""));
17699
+ String activeRelease = value.optString("activeRelease", "");
17700
+ File activePath = new File(snapshotRoot(context), activeRelease);
17701
+ boolean hasActive = validRelease(activeRelease) && activePath.isDirectory();
17702
+ String previous = hasActive ? activePath.getAbsolutePath() : "";
17698
17703
  long started = value.optLong("pendingStartedAt", System.currentTimeMillis());
17699
17704
  long duration = Math.max(0, System.currentTimeMillis() - started);
17700
17705
  value.remove("pendingRelease");
@@ -18888,7 +18893,7 @@ var connectTarget = async (target) => {
18888
18893
  };
18889
18894
  var isTransientEvaluationError = (error) => {
18890
18895
  const message = error instanceof Error ? error.message : String(error);
18891
- return /execution context|cannot find context|inspected target navigated|context.*destroyed/iu.test(message);
18896
+ return /execution context|cannot find context|inspected target navigated|context.*destroyed|CDP Runtime\.evaluate timed out/iu.test(message);
18892
18897
  };
18893
18898
  var createWaitFor = (evaluate) => async (expression, options = {}) => {
18894
18899
  const timeoutMs = options.timeoutMs ?? CDP_COMMAND_TIMEOUT_MS;
@@ -19324,6 +19329,12 @@ var verifyHealthModule = (config, module) => {
19324
19329
  if (typeof module.registry.inspectUpdateHealth !== "function" || typeof module.registry.issueUpdateHealthToken !== "function" || typeof module.registry.recordUpdateHealth !== "function")
19325
19330
  throw new TypeError("Mobile update fleet health is enabled but the registry is not provisioned for it. Run `absolute mobile update provision --force`.");
19326
19331
  };
19332
+ var verifyRolloutModule = (config, module) => {
19333
+ if (!config.updateServer?.rollout)
19334
+ return;
19335
+ 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")
19336
+ throw new TypeError("Mobile update rollout orchestration is enabled but the registry is not provisioned for it. Run `absolute mobile update provision --force`.");
19337
+ };
19327
19338
  var expoSigningOptions = (config) => {
19328
19339
  if (!config.updates?.expoCodeSigning)
19329
19340
  return;
@@ -19357,6 +19368,7 @@ var inspectAbsoluteMobileUpdateServer = async (config, projectRoot) => {
19357
19368
  const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, config.updateServer?.registryModule);
19358
19369
  await verifyDurableModule(module);
19359
19370
  verifyHealthModule(config, module);
19371
+ verifyRolloutModule(config, module);
19360
19372
  if (config.engine === "expo")
19361
19373
  expoSigningOptions(config);
19362
19374
  return module.metadata;
@@ -19431,17 +19443,25 @@ export default createMobileUpdateRegistry({
19431
19443
  };
19432
19444
  var renderAbsoluteMobileUpdateRegistry = (options) => {
19433
19445
  const source = renderAbsoluteMobileUpdateRegistryBase(options);
19434
- if (!options.health)
19435
- return source;
19436
- const secret = options.storage === "local" ? `process.env.${options.health.secretEnv} ?? 'absolutejs-local-health-secret-not-for-production'` : `required('${options.health.secretEnv}')`;
19437
- const health = ` health: {
19446
+ let generated = "";
19447
+ if (options.health) {
19448
+ const secret = options.storage === "local" ? `process.env.${options.health.secretEnv} ?? 'absolutejs-local-health-secret-not-for-production'` : `required('${options.health.secretEnv}')`;
19449
+ generated += ` health: {
19438
19450
  autoPause: { failureRate: ${options.health.failureRate}, minimumReports: ${options.health.minimumReports} },
19439
19451
  secret: ${secret}
19440
19452
  },
19441
19453
  `;
19454
+ }
19455
+ if (options.rollout)
19456
+ generated += ` rollout: ${JSON.stringify(options.rollout, null, "\t").replaceAll(`
19457
+ `, `
19458
+ `)},
19459
+ `;
19460
+ if (!generated)
19461
+ return source;
19442
19462
  return source.replace(`export default createMobileUpdateRegistry({
19443
19463
  `, `export default createMobileUpdateRegistry({
19444
- ${health}`);
19464
+ ${generated}`);
19445
19465
  };
19446
19466
  var writeAbsoluteMobileUpdateRegistry = async (options) => {
19447
19467
  const path = projectPath(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
@@ -19455,6 +19475,7 @@ var writeAbsoluteMobileUpdateRegistry = async (options) => {
19455
19475
  await mkdir5(dirname4(path), { recursive: true });
19456
19476
  await Bun.write(path, renderAbsoluteMobileUpdateRegistry({
19457
19477
  ...options.health ? { health: options.health } : {},
19478
+ ...options.rollout ? { rollout: options.rollout } : {},
19458
19479
  publicKeys: options.publicKeys,
19459
19480
  storage: options.storage
19460
19481
  }));
@@ -22016,14 +22037,7 @@ var lifecycleMethod = (publisher, name) => {
22016
22037
  throw new TypeError(`Mobile update registry does not support ${name}. Re-run \`absolute mobile update provision --force\` after upgrading @absolutejs/deploy.`);
22017
22038
  return method;
22018
22039
  };
22019
- var inspectAbsoluteMobileUpdateHealth = async (options) => {
22020
- const report = await lifecycleMethod(options.publisher, "inspectUpdateHealth")({
22021
- appId: options.appId,
22022
- channel: options.channel,
22023
- ...options.releaseId ? { releaseId: options.releaseId } : {}
22024
- });
22025
- if (report === null)
22026
- return null;
22040
+ var validateHealthReport = (report, options) => {
22027
22041
  if (!object3(report) || report.appId !== options.appId || report.channel !== options.channel || options.releaseId !== undefined && report.releaseId !== options.releaseId || typeof report.paused !== "boolean" || ![
22028
22042
  report.activated,
22029
22043
  report.downloaded,
@@ -22044,6 +22058,45 @@ var inspectAbsoluteMobileUpdateHealth = async (options) => {
22044
22058
  throw new TypeError("Mobile update registry returned an invalid health report.");
22045
22059
  return report;
22046
22060
  };
22061
+ var inspectAbsoluteMobileUpdateHealth = async (options) => {
22062
+ const report = await lifecycleMethod(options.publisher, "inspectUpdateHealth")({
22063
+ appId: options.appId,
22064
+ channel: options.channel,
22065
+ ...options.releaseId ? { releaseId: options.releaseId } : {}
22066
+ });
22067
+ if (report === null)
22068
+ return null;
22069
+ return validateHealthReport(report, options);
22070
+ };
22071
+ var validateRolloutReport = (report, options) => {
22072
+ validateHealthReport(report, options);
22073
+ const validStage = (stage) => stage === undefined || object3(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;
22074
+ 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"))
22075
+ throw new TypeError("Mobile update registry returned an invalid rollout report.");
22076
+ return report;
22077
+ };
22078
+ var inspectAbsoluteMobileUpdateRollout = async (options) => {
22079
+ const report = await lifecycleMethod(options.publisher, "inspectUpdateRollout")({ appId: options.appId, channel: options.channel });
22080
+ return report === null ? null : validateRolloutReport(report, options);
22081
+ };
22082
+ var mutateAbsoluteMobileUpdateRollout = async (method, options) => validateRolloutReport(await lifecycleMethod(options.publisher, method)({
22083
+ appId: options.appId,
22084
+ channel: options.channel,
22085
+ ...method === "advanceUpdateRollout" && options.rollout !== undefined ? { rollout: options.rollout } : {},
22086
+ ...options.signal ? { signal: options.signal } : {}
22087
+ }), options);
22088
+ var advanceAbsoluteMobileUpdateRollout = (options) => mutateAbsoluteMobileUpdateRollout("advanceUpdateRollout", options);
22089
+ var cancelAbsoluteMobileUpdateRollout = (options) => mutateAbsoluteMobileUpdateRollout("cancelUpdateRollout", options);
22090
+ var pauseAbsoluteMobileUpdateRollout = (options) => mutateAbsoluteMobileUpdateRollout("pauseUpdateRollout", options);
22091
+ var reconcileAbsoluteMobileUpdateRollout = async (options) => {
22092
+ const report = await lifecycleMethod(options.publisher, "reconcileUpdateRollout")({
22093
+ appId: options.appId,
22094
+ channel: options.channel,
22095
+ ...options.signal ? { signal: options.signal } : {}
22096
+ });
22097
+ return report === null ? null : validateRolloutReport(report, options);
22098
+ };
22099
+ var resumeAbsoluteMobileUpdateRollout = (options) => mutateAbsoluteMobileUpdateRollout("resumeUpdateRollout", options);
22047
22100
  var validOptionalCounters = (values) => values.every((value) => value === undefined) || values.every((value) => Number.isSafeInteger(value) && (value ?? -1) >= 0);
22048
22101
  var validateStorageIdentity = (result, appId) => {
22049
22102
  if (!object3(result) || result.appId !== appId || !Array.isArray(result.releases) || ![
@@ -22830,6 +22883,7 @@ var mobileUpdatePublisher = async (args) => {
22830
22883
  throw new TypeError("Mobile updates are not configured.");
22831
22884
  const modulePath = valueAfter(args, "--registry") ?? mobile.updateServer.registryModule;
22832
22885
  return {
22886
+ mobile,
22833
22887
  projectRoot,
22834
22888
  publisher: await loadAbsoluteMobileUpdatePublisher(projectRoot, modulePath)
22835
22889
  };
@@ -22843,7 +22897,7 @@ var provisionMobileUpdate = async (args) => {
22843
22897
  throw new TypeError("--storage must be local or s3.");
22844
22898
  const modulePath = valueAfter(args, "--registry") ?? mobile.updateServer.registryModule;
22845
22899
  const packages = [
22846
- "@absolutejs/deploy@0.25.11",
22900
+ "@absolutejs/deploy@0.25.13",
22847
22901
  "@absolutejs/blob@0.5.2",
22848
22902
  ...requestedStorage === "s3" ? [
22849
22903
  "@aws-sdk/client-s3@3.1095.0",
@@ -22857,6 +22911,7 @@ var provisionMobileUpdate = async (args) => {
22857
22911
  const path = await writeAbsoluteMobileUpdateRegistry({
22858
22912
  force: args.includes("--force"),
22859
22913
  ...mobile.updateServer.health ? { health: mobile.updateServer.health } : {},
22914
+ ...mobile.updateServer.rollout ? { rollout: mobile.updateServer.rollout } : {},
22860
22915
  modulePath,
22861
22916
  projectRoot,
22862
22917
  publicKeys: mobile.updates.publicKeys,
@@ -22885,12 +22940,12 @@ var publishMobileUpdate = async (args) => {
22885
22940
  });
22886
22941
  if (!releaseDirectory)
22887
22942
  throw new TypeError("mobile update publish requires a release directory.");
22888
- const { projectRoot, publisher } = await mobileUpdatePublisher(args);
22943
+ const { mobile, projectRoot, publisher } = await mobileUpdatePublisher(args);
22889
22944
  const result = await publishAbsoluteMobileUpdate({
22890
22945
  projectRoot,
22891
22946
  publisher,
22892
22947
  releaseDirectory,
22893
- rollout: updateRollout(args, 0.05)
22948
+ rollout: updateRollout(args, mobile.updateServer?.rollout?.stages[0]?.rollout ?? 0.05)
22894
22949
  });
22895
22950
  console.log(`${result.reused ? "Reused" : "Published"} mobile update ${result.releaseId} to ${result.channel} at ${Math.round(result.rollout * 100)}%.`);
22896
22951
  if (result.storedBytes !== undefined && result.reusedBytes !== undefined && result.storedFiles !== undefined && result.reusedFiles !== undefined)
@@ -22994,23 +23049,87 @@ var inspectMobileUpdateHealth = async (args) => {
22994
23049
  if (!mobile.updates)
22995
23050
  throw new TypeError("mobile update status requires mobile.updates config.");
22996
23051
  const { publisher } = await mobileUpdatePublisher(args);
22997
- const report = await inspectAbsoluteMobileUpdateHealth({
23052
+ const releaseId = valueAfter(args, "--release");
23053
+ const rolloutReport = mobile.updateServer?.rollout ? await inspectAbsoluteMobileUpdateRollout({
23054
+ appId: mobile.appId,
23055
+ channel: mobile.updates.channel,
23056
+ publisher
23057
+ }) : undefined;
23058
+ const report = mobile.updateServer?.rollout ? rolloutReport : await inspectAbsoluteMobileUpdateHealth({
22998
23059
  appId: mobile.appId,
22999
23060
  channel: mobile.updates.channel,
23000
23061
  publisher,
23001
- ...valueAfter(args, "--release") ? { releaseId: valueAfter(args, "--release") } : {}
23062
+ ...releaseId ? { releaseId } : {}
23002
23063
  });
23003
23064
  if (args.includes("--json"))
23004
23065
  console.log(JSON.stringify(report, null, 2));
23005
23066
  else if (!report)
23006
23067
  console.log(`No active mobile update exists on ${mobile.updates.channel}.`);
23007
23068
  else {
23008
- console.log(`${report.releaseId} is ${report.paused ? "PAUSED" : "active"} at ${Math.round(report.rollout * 100)}%: ${report.terminalReports} terminal reports, ${report.failures} failures (${(report.failureRate * 100).toFixed(1)}%).`);
23069
+ console.log(`${report.releaseId} is ${rolloutReport?.status.toUpperCase() ?? (report.paused ? "PAUSED" : "ACTIVE")} at ${Math.round(report.rollout * 100)}%: ${report.terminalReports} terminal reports, ${report.failures} failures (${(report.failureRate * 100).toFixed(1)}%).`);
23070
+ if (rolloutReport)
23071
+ console.log(` Stage ${rolloutReport.currentStage + 1} entered ${rolloutReport.enteredAt}; advancement is ${rolloutReport.automatic ? "automatic" : "manual"}${rolloutReport.nextStage ? `, next ${Math.round(rolloutReport.nextStage.rollout * 100)}%` : ""}${rolloutReport.pausedBy ? `, paused by ${rolloutReport.pausedBy}` : ""}.`);
23009
23072
  console.log(` ${report.activated} activated, ${report.rolledBack} rolled back, ${report.quarantined} quarantined, ${report.downloaded} downloaded, ${report.downloadFailed} download failures.`);
23010
23073
  console.log(` Transfer: ${formatBytes(report.transfer.downloadedBytes)} downloaded, ${formatBytes(report.transfer.avoidedBytes)} avoided (${formatBytes(report.transfer.resumedBytes)} resumed, ${formatBytes(report.transfer.reusedBytes)} reused).`);
23011
23074
  }
23012
23075
  return report;
23013
23076
  };
23077
+ var MOBILE_UPDATE_ROLLOUT_ACTION_LABEL = {
23078
+ advance: "Advanced",
23079
+ cancel: "Cancelled",
23080
+ pause: "Paused",
23081
+ reconcile: "Reconciled",
23082
+ resume: "Resumed"
23083
+ };
23084
+ var isMobileUpdateRolloutAction = (value) => value !== undefined && Object.hasOwn(MOBILE_UPDATE_ROLLOUT_ACTION_LABEL, value);
23085
+ var controlMobileUpdateRollout = async (action, args) => {
23086
+ const startedAt = performance.now();
23087
+ const { mobile } = await loadMobile(valueAfter(args, "--config"));
23088
+ if (!mobile.updates || !mobile.updateServer?.rollout)
23089
+ throw new TypeError(`mobile update ${action} requires mobile.updates.server.rollout config.`);
23090
+ const { publisher } = await mobileUpdatePublisher(args);
23091
+ const options = {
23092
+ appId: mobile.appId,
23093
+ channel: mobile.updates.channel,
23094
+ publisher
23095
+ };
23096
+ const requestedRollout = valueAfter(args, "--rollout");
23097
+ let report;
23098
+ switch (action) {
23099
+ case "advance":
23100
+ report = await advanceAbsoluteMobileUpdateRollout({
23101
+ ...options,
23102
+ ...requestedRollout ? { rollout: updateRollout(args) } : {}
23103
+ });
23104
+ break;
23105
+ case "cancel":
23106
+ report = await cancelAbsoluteMobileUpdateRollout(options);
23107
+ break;
23108
+ case "pause":
23109
+ report = await pauseAbsoluteMobileUpdateRollout(options);
23110
+ break;
23111
+ case "reconcile":
23112
+ report = await reconcileAbsoluteMobileUpdateRollout(options);
23113
+ break;
23114
+ case "resume":
23115
+ report = await resumeAbsoluteMobileUpdateRollout(options);
23116
+ }
23117
+ if (args.includes("--json"))
23118
+ console.log(JSON.stringify(report, null, 2));
23119
+ else if (!report)
23120
+ console.log(`No active mobile update exists on ${mobile.updates.channel}.`);
23121
+ else
23122
+ console.log(`${MOBILE_UPDATE_ROLLOUT_ACTION_LABEL[action]} ${report.releaseId}: ${report.status.toUpperCase()} at ${Math.round(report.rollout * 100)}% (stage ${report.currentStage + 1}).`);
23123
+ sendTelemetryEvent("mobile:update-rollout", {
23124
+ action,
23125
+ durationMs: Math.round(performance.now() - startedAt),
23126
+ ...report ? {
23127
+ automatic: report.automatic,
23128
+ status: report.status
23129
+ } : { status: "empty" }
23130
+ });
23131
+ return report;
23132
+ };
23014
23133
  var collectMobileUpdates = async (args) => {
23015
23134
  const startedAt = performance.now();
23016
23135
  const { mobile } = await loadMobile(valueAfter(args, "--config"));
@@ -24393,6 +24512,10 @@ var runMobile = async (args) => {
24393
24512
  await inspectMobileUpdateHealth(args.slice(2));
24394
24513
  return;
24395
24514
  }
24515
+ if (command === "update" && isMobileUpdateRolloutAction(args[1])) {
24516
+ await controlMobileUpdateRollout(args[1], args.slice(2));
24517
+ return;
24518
+ }
24396
24519
  if (command === "update" && args[1] === "gc") {
24397
24520
  await collectMobileUpdates(args.slice(2));
24398
24521
  return;
@@ -24405,7 +24528,7 @@ var runMobile = async (args) => {
24405
24528
  await publishIos(args.slice(2));
24406
24529
  return;
24407
24530
  }
24408
- throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--release id] [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json]> [--config path]");
24531
+ throw new TypeError("Usage: absolute mobile <pair mac <name> <user@host> [--port n] [--workspace path] | remotes [inspect [name] [--json] | clean [name] --yes | --json] | unpair mac <name> | init [--no-native] [--force] | sync [ios|android] | inspect [--json] [--require-bundle] | associations [--outdir dir] [--verify] | ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] | doctor [ios|android|release [ios|android]] [--remote name] [--json|--fix [--yes]] | build <android|ios> [server-entry] [--remote name] [--outdir dir] [--web-outdir dir] [--unsigned] | update provision [--storage local|s3] [--registry module] [--force] [--yes] | update signing generate --private-key path [--certificate path] [--public-key path] [--key-id id] [--common-name name] [--validity-years n] | update build [server-entry] --classification bug-fix|content|security --key-id id --signing-key path --within-submitted-purpose [--outdir dir] [--web-outdir dir] | update publish <release-directory> [--rollout fraction] [--registry module] | update promote --release id --rollout fraction [--registry module] | update rollback [--release id] [--registry module] | update status [--registry module] [--json] | update advance [--rollout fraction] [--registry module] [--json] | update pause|resume|cancel|reconcile [--registry module] [--json] | update storage [--retain count] [--min-age-days days] [--registry module] [--json] | update gc [--retain count] [--min-age-days days] [--grace-days days] [--apply] [--registry module] [--json] | publish android [server-entry] [--registry module] [--channel name] [--play-track track] [--play-status completed|draft|halted|in-progress] [--play-rollout fraction] [--play-name name] [--play-notes language=text] [--play-update-priority 0..5] [--play-hold-review] [--play-cancel-existing-review] [--outdir dir] [--web-outdir dir] [--unsigned] | publish ios [server-entry] [--remote name] [--registry module] [--channel name] [--testflight-group name-or-id] [--testflight-notes locale=text] [--testflight-submit-review] [--outdir dir] [--web-outdir dir] [--unsigned] | test android [--route path] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--serial id] [--artifacts dir] [--json]> [--config path]");
24409
24532
  };
24410
24533
  export {
24411
24534
  runMobile
@@ -1,8 +1,8 @@
1
1
  // @bun
2
2
  import {
3
3
  start
4
- } from "./index-9rk7v35t.js";
5
- import"./index-x48brywr.js";
4
+ } from "./index-39j0dhq7.js";
5
+ import"./index-jqdt7841.js";
6
6
  import"./index-bzs19w3r.js";
7
7
  import"./index-s2tazax7.js";
8
8
  import"./index-fc9vxw56.js";
@@ -10,7 +10,7 @@ import"./index-r8x3839e.js";
10
10
  import"./index-06cmrcdq.js";
11
11
  import"./index-9kwmb3c0.js";
12
12
  import"./index-w5vswatm.js";
13
- import"./index-3tsmbyze.js";
13
+ import"./index-rd2hbttb.js";
14
14
  import"./index-tame6e2c.js";
15
15
  import"./index-ar4gz15x.js";
16
16
  import"./index-t84wxk1p.js";