@absolutejs/absolute 0.20.0-beta.81 → 0.20.0-beta.83

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/cli/index.js CHANGED
@@ -203,7 +203,7 @@ if (command === "dev") {
203
203
  sendTelemetryEvent("cli:command", {
204
204
  command: `mobile:${workspaceCommand ?? "unknown"}`
205
205
  });
206
- const { runMobile } = await import("./mobile-rdwdcks1.js");
206
+ const { runMobile } = await import("./mobile-9dx39bd3.js");
207
207
  try {
208
208
  await runMobile(args);
209
209
  } catch (error) {
@@ -2,6 +2,9 @@
2
2
  import {
3
3
  installPackages
4
4
  } from "./index-zh6hhrwy.js";
5
+ import {
6
+ formatBytes
7
+ } from "./index-9r7n9dqp.js";
5
8
  import {
6
9
  start
7
10
  } from "./index-q78x1k9q.js";
@@ -19292,11 +19295,29 @@ var loadAbsoluteMobileUpdateServerModule = async (projectRoot, requestedModulePa
19292
19295
  const registry = loaded.default ?? loaded.registry;
19293
19296
  if (!isRegistry(registry))
19294
19297
  throw new TypeError("Mobile update registry must implement publication, promotion, rollback, resolution, and file reads.");
19298
+ const metadata = serverMetadata(loaded.absoluteMobileUpdateServer);
19299
+ const verifier = loaded.verifyAbsoluteMobileUpdateServer;
19300
+ if (metadata.storage === "durable" && typeof verifier !== "function")
19301
+ throw new TypeError("Mobile update registries marked durable must export verifyAbsoluteMobileUpdateServer(). Re-run `absolute mobile update provision --storage s3 --force` or provide an active durability check.");
19295
19302
  return {
19296
- metadata: serverMetadata(loaded.absoluteMobileUpdateServer),
19297
- registry
19303
+ metadata,
19304
+ registry,
19305
+ ...typeof verifier === "function" ? {
19306
+ verifyDurability: async () => {
19307
+ await verifier();
19308
+ }
19309
+ } : {}
19298
19310
  };
19299
19311
  };
19312
+ var verifyDurableModule = async (module) => {
19313
+ if (module.metadata.storage !== "durable")
19314
+ throw new TypeError("Mobile production updates require durable object storage. Re-run `absolute mobile update provision --storage s3 --force` or configure a durable adapter.");
19315
+ try {
19316
+ await module.verifyDurability?.();
19317
+ } catch (error) {
19318
+ 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 });
19319
+ }
19320
+ };
19300
19321
  var expoSigningOptions = (config) => {
19301
19322
  if (!config.updates?.expoCodeSigning)
19302
19323
  return;
@@ -19328,8 +19349,7 @@ var inspectAbsoluteMobileUpdateServer = async (config, projectRoot) => {
19328
19349
  if (!config.updates)
19329
19350
  return;
19330
19351
  const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, config.updateServer?.registryModule);
19331
- if (module.metadata.storage !== "durable")
19332
- throw new TypeError("Mobile production updates require durable object storage; the configured registry is local-only.");
19352
+ await verifyDurableModule(module);
19333
19353
  if (config.engine === "expo")
19334
19354
  expoSigningOptions(config);
19335
19355
  return module.metadata;
@@ -19358,7 +19378,8 @@ export default createMobileUpdateRegistry({
19358
19378
  store
19359
19379
  });
19360
19380
  `;
19361
- return `import { S3Client } from '@aws-sdk/client-s3';
19381
+ return `import { randomUUID } from 'node:crypto';
19382
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
19362
19383
  import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
19363
19384
  import { createMobileUpdateRegistry } from '@absolutejs/deploy/mobile-update';
19364
19385
 
@@ -19370,6 +19391,7 @@ const required = (name: string) => {
19370
19391
  return value;
19371
19392
  };
19372
19393
 
19394
+ const bucket = required('ABSOLUTE_MOBILE_UPDATE_S3_BUCKET');
19373
19395
  const client = new S3Client({
19374
19396
  region: process.env.ABSOLUTE_MOBILE_UPDATE_S3_REGION ?? 'auto',
19375
19397
  forcePathStyle: process.env.ABSOLUTE_MOBILE_UPDATE_S3_FORCE_PATH_STYLE === '1',
@@ -19377,10 +19399,22 @@ const client = new S3Client({
19377
19399
  ? { endpoint: process.env.ABSOLUTE_MOBILE_UPDATE_S3_ENDPOINT }
19378
19400
  : {})
19379
19401
  });
19380
- const store = awsS3BlobStore({
19381
- bucket: required('ABSOLUTE_MOBILE_UPDATE_S3_BUCKET'),
19382
- client
19383
- });
19402
+ const store = awsS3BlobStore({ bucket, client });
19403
+
19404
+ export const verifyAbsoluteMobileUpdateServer = async () => {
19405
+ const key = \`absolutejs/mobile-updates/_health/\${randomUUID()}\`;
19406
+ const expected = randomUUID();
19407
+ let stored = false;
19408
+ try {
19409
+ await client.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: expected }));
19410
+ stored = true;
19411
+ const response = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
19412
+ if ((await response.Body?.transformToString()) !== expected)
19413
+ throw new Error('Durability probe read did not match its write.');
19414
+ } finally {
19415
+ if (stored) await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
19416
+ }
19417
+ };
19384
19418
 
19385
19419
  export default createMobileUpdateRegistry({
19386
19420
  publicKeys: ${publicKeysSource(options.publicKeys)},
@@ -19468,7 +19502,7 @@ var mobileUpdateServerCheck = async (config, projectRoot) => {
19468
19502
  return;
19469
19503
  try {
19470
19504
  const metadata = await inspectAbsoluteMobileUpdateServer(config, projectRoot);
19471
- return pass("updates.trusted-server", `The trusted update server uses durable ${metadata?.provider ?? "object"} storage, supports publish/promote/rollback, and has valid server-only signing material.`, config.updateServer?.registryModule);
19505
+ return pass("updates.trusted-server", `The trusted update server proved durable ${metadata?.provider ?? "object"} storage read/write/delete access, supports publish/promote/rollback, and has valid server-only signing material.`, config.updateServer?.registryModule);
19472
19506
  } catch (error) {
19473
19507
  return fail("updates.trusted-server", error instanceof Error ? error.message : "The trusted mobile update server is not release-ready.", config.updateServer?.registryModule ?? "mobile.update.ts", "Run `absolute mobile update provision --storage s3 --force`, provision its environment variables on the trusted server, and rerun the release doctor.");
19474
19508
  }
@@ -21954,6 +21988,45 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
21954
21988
  import { access as access11 } from "fs/promises";
21955
21989
  import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve11, sep as sep6 } from "path";
21956
21990
  import { pathToFileURL as pathToFileURL3 } from "url";
21991
+ var lifecycleMethod = (publisher, name) => {
21992
+ const method = publisher[name];
21993
+ if (typeof method !== "function")
21994
+ throw new TypeError(`Mobile update registry does not support ${name}. Re-run \`absolute mobile update provision --force\` after upgrading @absolutejs/deploy.`);
21995
+ return method;
21996
+ };
21997
+ var validateStorageIdentity = (result, appId) => {
21998
+ if (!object3(result) || result.appId !== appId || !Array.isArray(result.releases) || ![
21999
+ result.channelCount,
22000
+ result.reclaimableBytes,
22001
+ result.releaseBytes,
22002
+ result.releaseCount,
22003
+ result.totalBytes,
22004
+ result.totalObjectCount,
22005
+ result.untrackedBytes
22006
+ ].every((value) => Number.isSafeInteger(value) && value >= 0))
22007
+ throw new TypeError("Mobile update registry returned an invalid storage report.");
22008
+ return result;
22009
+ };
22010
+ var inspectAbsoluteMobileUpdateStorage = async (options) => validateStorageIdentity(await lifecycleMethod(options.publisher, "inspectUpdateStorage")({
22011
+ appId: options.appId,
22012
+ ...options.minAgeMs === undefined ? {} : { minAgeMs: options.minAgeMs },
22013
+ ...options.retainRecent === undefined ? {} : { retainRecent: options.retainRecent },
22014
+ ...options.signal ? { signal: options.signal } : {}
22015
+ }), options.appId);
22016
+ var pruneAbsoluteMobileUpdates = async (options) => {
22017
+ const result = await lifecycleMethod(options.publisher, "pruneUpdates")({
22018
+ appId: options.appId,
22019
+ ...options.apply === undefined ? {} : { apply: options.apply },
22020
+ ...options.gracePeriodMs === undefined ? {} : { gracePeriodMs: options.gracePeriodMs },
22021
+ ...options.minAgeMs === undefined ? {} : { minAgeMs: options.minAgeMs },
22022
+ ...options.retainRecent === undefined ? {} : { retainRecent: options.retainRecent },
22023
+ ...options.signal ? { signal: options.signal } : {}
22024
+ });
22025
+ validateStorageIdentity(result, options.appId);
22026
+ if (!Array.isArray(result.marked) || !Array.isArray(result.restored) || !Array.isArray(result.swept) || typeof result.dryRun !== "boolean" || !Number.isSafeInteger(result.reclaimedBytes) || result.reclaimedBytes < 0)
22027
+ throw new TypeError("Mobile update registry returned an invalid collection report.");
22028
+ return result;
22029
+ };
21957
22030
  var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21958
22031
  var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
21959
22032
  var projectPath3 = (projectRoot, requested, label) => {
@@ -22710,7 +22783,7 @@ var provisionMobileUpdate = async (args) => {
22710
22783
  throw new TypeError("--storage must be local or s3.");
22711
22784
  const modulePath = valueAfter(args, "--registry") ?? mobile.updateServer.registryModule;
22712
22785
  const packages = [
22713
- "@absolutejs/deploy@0.25.5",
22786
+ "@absolutejs/deploy@0.25.7",
22714
22787
  "@absolutejs/blob@0.5.2",
22715
22788
  ...requestedStorage === "s3" ? [
22716
22789
  "@aws-sdk/client-s3@3.1095.0",
@@ -22794,6 +22867,97 @@ var rollbackMobileUpdate = async (args) => {
22794
22867
  console.log(result.releaseId ? `Rolled ${result.channel} back to ${result.releaseId}.` : `Rolled ${result.channel} back to the embedded store build.`);
22795
22868
  return result;
22796
22869
  };
22870
+ var mobileUpdateDays = (args, flag) => {
22871
+ const value = valueAfter(args, flag);
22872
+ if (value === undefined)
22873
+ return;
22874
+ const parsed = Number(value);
22875
+ const milliseconds = parsed * 24 * 60 * 60 * 1000;
22876
+ if (!Number.isFinite(parsed) || parsed < 0 || !Number.isSafeInteger(milliseconds))
22877
+ throw new TypeError(`${flag} must be a non-negative number of days.`);
22878
+ return milliseconds;
22879
+ };
22880
+ var mobileUpdateRetention = (args) => {
22881
+ const retainedValue = valueAfter(args, "--retain");
22882
+ const retainRecent = retainedValue === undefined ? undefined : Number(retainedValue);
22883
+ if (retainRecent !== undefined && (!Number.isSafeInteger(retainRecent) || retainRecent < 0))
22884
+ throw new TypeError("--retain must be a non-negative integer.");
22885
+ return {
22886
+ minAgeMs: mobileUpdateDays(args, "--min-age-days"),
22887
+ retainRecent
22888
+ };
22889
+ };
22890
+ var printMobileUpdateStorage = (report) => {
22891
+ console.log(`${report.appId}: ${report.releaseCount} releases use ${formatBytes(report.releaseBytes)}; ${formatBytes(report.reclaimableBytes)} is eligible across ${report.totalObjectCount} stored objects.`);
22892
+ for (const release of report.releases) {
22893
+ let state = "eligible";
22894
+ if (release.protectedBy.length > 0)
22895
+ state = `kept: ${release.protectedBy.join(", ")}`;
22896
+ else if (release.markedAt)
22897
+ state = `marked ${release.markedAt}`;
22898
+ console.log(` ${release.releaseId} ${release.channel} ${formatBytes(release.bytes)} (${state})`);
22899
+ }
22900
+ if (report.untrackedBytes > 0)
22901
+ console.log(`${formatBytes(report.untrackedBytes)} is used by channels, collection markers, or incomplete/unrecognized objects and will not be swept as a release.`);
22902
+ };
22903
+ var inspectMobileUpdateStorage = async (args) => {
22904
+ const startedAt = performance.now();
22905
+ const { mobile } = await loadMobile(valueAfter(args, "--config"));
22906
+ if (!mobile.updates)
22907
+ throw new TypeError("mobile update storage requires mobile.updates config.");
22908
+ const policy = mobileUpdateRetention(args);
22909
+ const { publisher } = await mobileUpdatePublisher(args);
22910
+ const report = await inspectAbsoluteMobileUpdateStorage({
22911
+ appId: mobile.appId,
22912
+ publisher,
22913
+ ...policy.minAgeMs === undefined ? {} : { minAgeMs: policy.minAgeMs },
22914
+ ...policy.retainRecent === undefined ? {} : { retainRecent: policy.retainRecent }
22915
+ });
22916
+ if (args.includes("--json"))
22917
+ console.log(JSON.stringify(report, null, 2));
22918
+ else
22919
+ printMobileUpdateStorage(report);
22920
+ sendTelemetryEvent("mobile:update-storage", {
22921
+ durationMs: Math.round(performance.now() - startedAt),
22922
+ reclaimableReleaseCount: report.releases.filter((release) => release.protectedBy.length === 0).length,
22923
+ releaseCount: report.releaseCount
22924
+ });
22925
+ return report;
22926
+ };
22927
+ var collectMobileUpdates = async (args) => {
22928
+ const startedAt = performance.now();
22929
+ const { mobile } = await loadMobile(valueAfter(args, "--config"));
22930
+ if (!mobile.updates)
22931
+ throw new TypeError("mobile update gc requires mobile.updates config.");
22932
+ const policy = mobileUpdateRetention(args);
22933
+ const gracePeriodMs = mobileUpdateDays(args, "--grace-days");
22934
+ const { publisher } = await mobileUpdatePublisher(args);
22935
+ const result = await pruneAbsoluteMobileUpdates({
22936
+ appId: mobile.appId,
22937
+ apply: args.includes("--apply"),
22938
+ publisher,
22939
+ ...gracePeriodMs === undefined ? {} : { gracePeriodMs },
22940
+ ...policy.minAgeMs === undefined ? {} : { minAgeMs: policy.minAgeMs },
22941
+ ...policy.retainRecent === undefined ? {} : { retainRecent: policy.retainRecent }
22942
+ });
22943
+ if (args.includes("--json"))
22944
+ console.log(JSON.stringify(result, null, 2));
22945
+ else {
22946
+ printMobileUpdateStorage(result);
22947
+ if (result.dryRun)
22948
+ console.log("No storage was changed. Re-run with --apply to mark eligible releases and sweep releases whose grace period has elapsed.");
22949
+ else
22950
+ console.log(`Collection applied: ${result.marked.length} marked, ${result.restored.length} restored, ${result.swept.length} swept, ${formatBytes(result.reclaimedBytes)} reclaimed.`);
22951
+ }
22952
+ sendTelemetryEvent("mobile:update-gc", {
22953
+ applied: !result.dryRun,
22954
+ durationMs: Math.round(performance.now() - startedAt),
22955
+ markedCount: result.marked.length,
22956
+ restoredCount: result.restored.length,
22957
+ sweptCount: result.swept.length
22958
+ });
22959
+ return result;
22960
+ };
22797
22961
  var requireValueAfter = (args, flag) => {
22798
22962
  const value = valueAfter(args, flag);
22799
22963
  if (!value || value.startsWith("-")) {
@@ -24134,6 +24298,14 @@ var runMobile = async (args) => {
24134
24298
  await rollbackMobileUpdate(args.slice(2));
24135
24299
  return;
24136
24300
  }
24301
+ if (command === "update" && args[1] === "storage") {
24302
+ await inspectMobileUpdateStorage(args.slice(2));
24303
+ return;
24304
+ }
24305
+ if (command === "update" && args[1] === "gc") {
24306
+ await collectMobileUpdates(args.slice(2));
24307
+ return;
24308
+ }
24137
24309
  if (command === "publish" && args[1] === "android") {
24138
24310
  await publishAndroid(args.slice(2));
24139
24311
  return;
@@ -24142,7 +24314,7 @@ var runMobile = async (args) => {
24142
24314
  await publishIos(args.slice(2));
24143
24315
  return;
24144
24316
  }
24145
- 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] | 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] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
24317
+ 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 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] | test ios [--device identifier [--remote name] | --udid id] [--wait-for-hmr] [--report [dir]] [--timeout ms] [--port n] [--artifacts dir] [--json]> [--config path]");
24146
24318
  };
24147
24319
  export {
24148
24320
  runMobile