@absolutejs/absolute 0.20.0-beta.63 → 0.20.0-beta.64

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.
@@ -401,770 +401,770 @@ var normalizeSlug = (str) => str.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-
401
401
  return normalizeSlug(str).split(/[-_]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()).join("");
402
402
  };
403
403
 
404
- // src/mobile/nativeAuth.ts
405
- import { readFileSync as readFileSync2 } from "fs";
406
- import { join as join11 } from "path";
407
- var ABSOLUTE_AUTH_PACKAGE = "@absolutejs/auth", ABSOLUTE_EXPO_AUTH_CORE_VERSION = "0.75.6", ABSOLUTE_EXPO_AUTH_PACKAGE = "@absolutejs/auth-expo", ABSOLUTE_EXPO_AUTH_VERSION = "0.0.2", ABSOLUTE_EXPO_SYNC_CORE_VERSION = "2.31.0", ABSOLUTE_EXPO_SYNC_PACKAGE = "@absolutejs/sync-expo", ABSOLUTE_EXPO_SYNC_VERSION = "0.0.2", ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV = "ABSOLUTE_AUTH_NATIVE_CLIENTS", ABSOLUTE_NATIVE_AUTH_SCOPES, ABSOLUTE_SYNC_PACKAGE = "@absolutejs/sync", readPackageManifest = (projectRoot) => {
408
- try {
409
- return JSON.parse(readFileSync2(join11(projectRoot, "package.json"), "utf8"));
410
- } catch {
404
+ // src/mobile/deviceCapabilities.ts
405
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
406
+ import { extname as extname3, join as join11, relative as relative9, resolve as resolve10 } from "path";
407
+ import { fileURLToPath } from "url";
408
+ import ts from "typescript";
409
+ var DEVICES_PACKAGE = "@absolutejs/devices", ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES, ADAPTERS, SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, providerModulePattern = (provider) => new RegExp(`^@absolutejs/devices-${provider}/[a-z][a-z0-9-]*$`, "u"), providerPackagePattern = (provider) => provider === "capacitor" ? /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u : /^(?:expo-[a-z][a-z0-9-]*|@react-native-[a-z0-9-]+\/[a-z][a-z0-9-]*)@\d+\.\d+\.\d+$/u, providerLabel = (provider) => provider === "capacitor" ? "Capacitor" : "Expo", ANDROID_PERMISSION_PATTERN, IOS_USAGE_DESCRIPTIONS, IOS_PRIVACY_ACCESSED_API_REASONS, IOS_PRIVACY_ACCESSED_APIS, object = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson = (path) => {
410
+ const value = JSON.parse(readFileSync2(path, "utf8"));
411
+ if (!object(value))
412
+ throw new TypeError(`${path} must contain an object.`);
413
+ return value;
414
+ }, text = (value, field2) => {
415
+ if (typeof value !== "string" || value.length === 0)
416
+ throw new TypeError(`${field2} must be a non-empty string.`);
417
+ return value;
418
+ }, androidPermissions = (value, field2) => {
419
+ if (value === undefined)
411
420
  return;
421
+ if (!object(value))
422
+ throw new TypeError(`${field2} must be an object.`);
423
+ const { permissions } = value;
424
+ if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
425
+ throw new TypeError(`${field2}.permissions must contain Android permission names.`);
426
+ return [...permissions];
427
+ }, iosPrivacyAccessedApis = (value, field2) => {
428
+ if (value === undefined)
429
+ return;
430
+ if (!object(value))
431
+ throw new TypeError(`${field2} must be an object.`);
432
+ const privacy = {};
433
+ for (const api of IOS_PRIVACY_ACCESSED_APIS) {
434
+ const reasons = value[api];
435
+ if (reasons === undefined)
436
+ continue;
437
+ const supported = IOS_PRIVACY_ACCESSED_API_REASONS[api];
438
+ if (!Array.isArray(reasons) || reasons.length === 0 || !reasons.every((reason) => typeof reason === "string" && supported.has(reason)))
439
+ throw new TypeError(`${field2} contains an unsupported API or reason.`);
440
+ privacy[api] = [...reasons];
412
441
  }
413
- }, packageManifestHas = (manifest, packageName) => {
414
- if (typeof manifest !== "object" || manifest === null)
415
- return false;
416
- return [
417
- Reflect.get(manifest, "dependencies"),
418
- Reflect.get(manifest, "devDependencies"),
419
- Reflect.get(manifest, "optionalDependencies"),
420
- Reflect.get(manifest, "peerDependencies")
421
- ].some((dependencies) => typeof dependencies === "object" && dependencies !== null && Object.hasOwn(dependencies, packageName));
422
- }, createAbsoluteMobileAuthManifest = (config) => {
423
- const scheme = config.deepLinkScheme ?? config.appId.toLowerCase();
442
+ if (Object.keys(value).some((api) => !IOS_PRIVACY_ACCESSED_APIS.some((known) => known === api)))
443
+ throw new TypeError(`${field2} contains an unsupported API or reason.`);
444
+ return privacy;
445
+ }, iosNativeRequirements = (value, field2) => {
446
+ if (value === undefined)
447
+ return;
448
+ if (!object(value))
449
+ throw new TypeError(`${field2} must be an object.`);
450
+ const {
451
+ privacyAccessedApis,
452
+ pushNotifications,
453
+ systemBars: systemBars2,
454
+ usageDescriptions
455
+ } = value;
456
+ if (pushNotifications !== undefined && pushNotifications !== true)
457
+ throw new TypeError(`${field2}.pushNotifications must be true.`);
458
+ if (systemBars2 !== undefined && systemBars2 !== true)
459
+ throw new TypeError(`${field2}.systemBars must be true.`);
460
+ if (usageDescriptions !== undefined && (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose))))
461
+ throw new TypeError(`${field2}.usageDescriptions contains an unsupported purpose.`);
462
+ const privacy = iosPrivacyAccessedApis(privacyAccessedApis, `${field2}.privacyAccessedApis`);
424
463
  return {
425
- clientId: `absolutejs-native:${config.appId}`,
426
- issuer: config.productionOrigin,
427
- redirectUri: `${scheme}://auth/callback`,
428
- scopes: [...ABSOLUTE_NATIVE_AUTH_SCOPES]
464
+ ...privacy === undefined ? {} : { privacyAccessedApis: privacy },
465
+ ...pushNotifications === true ? { pushNotifications: true } : {},
466
+ ...systemBars2 === true ? { systemBars: true } : {},
467
+ ...usageDescriptions === undefined ? {} : { usageDescriptions: [...usageDescriptions] }
429
468
  };
430
- }, installAbsoluteMobileAuthEnvironment = (projectRoot, config) => {
431
- const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
432
- const serialized = serializeAbsoluteMobileAuthEnvironment(config, auth);
433
- if (serialized === undefined)
434
- delete process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV];
435
- else
436
- process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV] = serialized;
437
- return auth;
438
- }, projectUsesAbsoluteAuth = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_AUTH_PACKAGE), projectUsesAbsoluteSync = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_SYNC_PACKAGE), resolveAbsoluteMobileAuthManifest = (projectRoot, config) => projectUsesAbsoluteAuth(projectRoot) ? createAbsoluteMobileAuthManifest(config) : undefined, serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefined ? undefined : JSON.stringify([
439
- {
440
- ...auth,
441
- name: `${config.appName} native app`
442
- }
443
- ]);
444
- var init_nativeAuth = __esm(() => {
445
- ABSOLUTE_NATIVE_AUTH_SCOPES = ["openid", "profile"];
446
- });
447
-
448
- // node_modules/@absolutejs/sync/dist/client/index.js
449
- var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients")), registry, pools, SyncLocalDataPolicyError, SyncLocalStoreSchemaError, positiveVersion = (value, label) => {
450
- if (!Number.isSafeInteger(value) || value < 1)
451
- throw new SyncLocalStoreSchemaError("INVALID_PLAN", `${label} must be a positive safe integer`);
452
- return value;
453
- }, isSchemaBundle = (schema) => ("components" in schema), validatePolicyMatch = (match, label) => {
454
- if (match.length === 0 || match.trim() !== match || /^\*+$/.test(match) || match.includes("**"))
455
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.match must be an exact name or a non-empty glob without adjacent wildcards.`);
456
- }, validateSyncLocalDataPolicy = (policy, label = "localData") => {
457
- if (policy.maxBytesPerNamespace !== undefined && (!Number.isSafeInteger(policy.maxBytesPerNamespace) || policy.maxBytesPerNamespace < 1))
458
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.maxBytesPerNamespace must be a positive safe integer.`);
459
- for (const [index, rule] of (policy.collections ?? []).entries()) {
460
- validatePolicyMatch(rule.match, `${label}.collections[${index}]`);
461
- if (rule.maxAgeMs !== undefined && (!Number.isSafeInteger(rule.maxAgeMs) || rule.maxAgeMs < 1))
462
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}].maxAgeMs must be a positive safe integer.`);
463
- if (rule.persistence === "memory-only" && rule.protection === "required")
464
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}] cannot require at-rest protection when it is memory-only.`);
465
- if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
466
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}] declares ${rule.sensitivity} data without required protection or memory-only persistence.`);
467
- }
468
- for (const [index, rule] of (policy.mutations ?? []).entries()) {
469
- validatePolicyMatch(rule.match, `${label}.mutations[${index}]`);
470
- if (rule.conflict !== undefined && rule.conflict.strategy !== "client-wins" && rule.conflict.strategy !== "manual" && rule.conflict.strategy !== "server-wins")
471
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.strategy is invalid.`);
472
- if (rule.conflict?.maxAttempts !== undefined && (!Number.isSafeInteger(rule.conflict.maxAttempts) || rule.conflict.maxAttempts < 1))
473
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts must be a positive safe integer.`);
474
- if (rule.conflict?.maxAttempts !== undefined && rule.conflict.strategy !== "client-wins")
475
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts is only valid for client-wins.`);
476
- if (rule.persistence === "memory-only" && rule.protection === "required")
477
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] cannot require at-rest protection when it is memory-only.`);
478
- if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
479
- throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] declares ${rule.sensitivity} arguments without required protection.`);
480
- }
481
- return policy;
482
- }, normalizeSyncLocalSchemaComponents = (schema = { version: 1 }) => {
483
- const components = isSchemaBundle(schema) ? [...schema.components] : [{ ...schema, id: "@absolutejs/app" }];
484
- const ids = new Set;
485
- for (const component of components) {
486
- if (typeof component.id !== "string" || component.id.trim() !== component.id || component.id.length === 0)
487
- throw new SyncLocalStoreSchemaError("INVALID_PLAN", "Sync schema component id must be non-empty and trimmed");
488
- if (ids.has(component.id))
489
- throw new SyncLocalStoreSchemaError("INVALID_PLAN", `Sync schema component "${component.id}" is declared more than once`);
490
- ids.add(component.id);
491
- if (component.localData)
492
- validateSyncLocalDataPolicy(component.localData, `${component.id}.localData`);
493
- }
494
- return components.sort((a, b) => a.id.localeCompare(b.id));
495
- }, resolveSyncLocalSchemaComponents = (storedVersions, schema = { version: 1 }) => {
496
- const components = normalizeSyncLocalSchemaComponents(schema).map((component) => {
497
- const current = resolveSyncLocalMigrations(component.version, component);
498
- return {
499
- id: component.id,
500
- ...resolveSyncLocalMigrations(storedVersions[component.id] ?? current.minimumCompatibleVersion, component)
469
+ }, parseProvider = (name, value, providerName) => {
470
+ if (!IDENTIFIER_PATTERN.test(name))
471
+ throw new TypeError("Device capability names must be identifiers.");
472
+ if (!object(value))
473
+ throw new TypeError(`Device capability ${name} must be an object.`);
474
+ const factory = text(value.factory, `${name}.factory`);
475
+ const module = text(value.module, `${name}.module`);
476
+ if (!IDENTIFIER_PATTERN.test(factory))
477
+ throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
478
+ if (!providerModulePattern(providerName).test(module))
479
+ throw new TypeError(`${name}.module must be an official devices-${providerName} subpath.`);
480
+ if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && providerPackagePattern(providerName).test(spec)))
481
+ throw new TypeError(`${name}.packages must contain exact official ${providerLabel(providerName)} package versions.`);
482
+ const { plugins } = value;
483
+ if (plugins !== undefined && (!Array.isArray(plugins) || !plugins.every((plugin) => typeof plugin === "string" && /^expo-[a-z][a-z0-9-]*$/u.test(plugin))))
484
+ throw new TypeError(`${name}.plugins must contain Expo config plugin names.`);
485
+ let native;
486
+ const { native: nativeMetadata } = value;
487
+ if (nativeMetadata !== undefined) {
488
+ if (!object(nativeMetadata))
489
+ throw new TypeError(`${name}.native must be an object.`);
490
+ const { android, ios } = nativeMetadata;
491
+ const permissions = androidPermissions(android, `${name}.native.android`);
492
+ const iosRequirements = iosNativeRequirements(ios, `${name}.native.ios`);
493
+ native = {
494
+ ...permissions === undefined ? {} : { android: { permissions } },
495
+ ...iosRequirements === undefined ? {} : { ios: iosRequirements }
501
496
  };
502
- });
503
- const active = new Set(components.map((component) => component.id));
504
- const orphanedComponents = Object.keys(storedVersions).filter((id) => !active.has(id)).sort();
505
- return { components, orphanedComponents };
506
- }, resolveSyncLocalMigrations = (storedVersion, schema = { version: 1 }) => {
507
- positiveVersion(storedVersion, "Stored Sync schema version");
508
- const targetVersion = positiveVersion(schema.version, "Target Sync schema version");
509
- const migrations = [...schema.migrations ?? []].sort((a, b) => a.toVersion - b.toVersion);
510
- const versions = new Set;
511
- for (const migration of migrations) {
512
- positiveVersion(migration.toVersion, "Sync migration toVersion");
513
- if (versions.has(migration.toVersion))
514
- throw new SyncLocalStoreSchemaError("INVALID_PLAN", `Sync migration ${migration.toVersion} is declared more than once`);
515
- versions.add(migration.toVersion);
516
- }
517
- const inferredMinimum = migrations[0] ? migrations[0].toVersion - 1 : targetVersion;
518
- const minimumCompatibleVersion = positiveVersion(schema.minimumCompatibleVersion ?? inferredMinimum, "Minimum compatible Sync schema version");
519
- if (minimumCompatibleVersion > targetVersion)
520
- throw new SyncLocalStoreSchemaError("INVALID_PLAN", "Minimum compatible Sync schema version cannot exceed its target");
521
- if (storedVersion > targetVersion)
522
- throw new SyncLocalStoreSchemaError("SCHEMA_TOO_NEW", `Stored Sync schema ${storedVersion} is newer than this runtime's schema ${targetVersion}`, { storedVersion, targetVersion });
523
- if (storedVersion < minimumCompatibleVersion)
524
- throw new SyncLocalStoreSchemaError("SCHEMA_TOO_OLD", `Stored Sync schema ${storedVersion} is older than the minimum compatible schema ${minimumCompatibleVersion}`, { storedVersion, targetVersion });
525
- const steps = [];
526
- for (let version = storedVersion + 1;version <= targetVersion; version++) {
527
- const migration = migrations.find((candidate) => candidate.toVersion === version);
528
- if (migration === undefined)
529
- throw new SyncLocalStoreSchemaError("MIGRATION_MISSING", `Sync migration ${version - 1} -> ${version} is missing`, { storedVersion, targetVersion });
530
- steps.push(migration);
531
497
  }
532
- return { minimumCompatibleVersion, steps, targetVersion };
533
- };
534
- var init_client = __esm(() => {
535
- RUNTIME_TRANSPORT = Symbol.for("@absolutejs/sync/client-runtime-transport");
536
- host = globalThis;
537
- registry = (() => {
538
- const existing = host[RUNTIME_TRANSPORT];
539
- if (isRegistry(existing))
540
- return existing;
541
- if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
542
- Reflect.set(existing, "clients", []);
543
- return existing;
544
- }
545
- const created = { clients: [], installations: [] };
546
- Object.defineProperty(host, RUNTIME_TRANSPORT, {
547
- configurable: false,
548
- enumerable: false,
549
- value: created,
550
- writable: false
551
- });
552
- return created;
553
- })();
554
- pools = new Map;
555
- SyncLocalDataPolicyError = class SyncLocalDataPolicyError extends Error {
556
- code;
557
- constructor(code, message) {
558
- super(message);
559
- this.name = "SyncLocalDataPolicyError";
560
- this.code = code;
561
- }
562
- };
563
- SyncLocalStoreSchemaError = class SyncLocalStoreSchemaError extends Error {
564
- code;
565
- storedVersion;
566
- targetVersion;
567
- constructor(code, message, versions = {}) {
568
- super(message);
569
- this.name = "SyncLocalStoreSchemaError";
570
- this.code = code;
571
- this.storedVersion = versions.storedVersion;
572
- this.targetVersion = versions.targetVersion;
573
- }
498
+ return {
499
+ factory,
500
+ module,
501
+ ...native === undefined ? {} : { native },
502
+ ...plugins === undefined ? {} : { plugins: [...plugins] },
503
+ packages: [...value.packages]
574
504
  };
575
- });
576
-
577
- // src/mobile/syncSchema.ts
578
- import { readFileSync as readFileSync3 } from "fs";
579
- import { dirname as dirname9, join as join12, resolve as resolve10 } from "path";
580
- var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value), manifestAt = (path) => {
581
- try {
582
- const value = JSON.parse(readFileSync3(path, "utf8"));
583
- return object(value) ? value : undefined;
584
- } catch {
585
- return;
586
- }
587
- }, localSchemaMetadata = (manifest) => {
588
- const absolutejs = Reflect.get(manifest, "absolutejs");
589
- if (!object(absolutejs))
590
- return;
591
- const sync = Reflect.get(absolutejs, "sync");
592
- if (!object(sync))
593
- return;
594
- return Reflect.get(sync, "localSchema");
595
- }, packageManifestPath = (projectRoot, packageName) => {
596
- let directory = resolve10(projectRoot);
597
- while (true) {
598
- const candidate = join12(directory, "node_modules", packageName, "package.json");
599
- const manifest = manifestAt(candidate);
600
- if (manifest && Reflect.get(manifest, "name") === packageName)
601
- return candidate;
602
- const parent = dirname9(directory);
603
- if (parent === directory)
604
- return;
605
- directory = parent;
606
- }
607
- }, metadataError = (id, detail) => new TypeError(`Invalid AbsoluteJS Sync schema metadata for ${id}: ${detail}`), positiveVersion2 = (value, id, field2) => {
608
- if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
609
- throw metadataError(id, `${field2} must be a positive safe integer.`);
610
- return value;
611
- }, nonEmpty = (value, id, field2) => {
612
- if (typeof value !== "string" || value.trim() !== value || value.length === 0)
613
- throw metadataError(id, `${field2} must be a non-empty trimmed string.`);
614
- return value;
615
- }, requireObject = (value, id, detail) => {
616
- if (!object(value))
617
- throw metadataError(id, detail);
618
- return value;
619
- }, unknownField = (record, key) => record[key], normalizeJsonValue2 = (value, id, field2) => {
620
- if (value === null || typeof value === "string" || typeof value === "boolean")
621
- return value;
622
- if (typeof value === "number" && Number.isFinite(value))
623
- return value;
624
- if (Array.isArray(value))
625
- return value.map((entry) => normalizeJsonValue2(entry, id, field2));
626
- if (object(value))
627
- return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
628
- key,
629
- normalizeJsonValue2(entry, id, field2)
630
- ]));
631
- throw metadataError(id, `${field2} must be JSON-safe.`);
632
- }, operation = (value, id, index) => {
633
- const record = requireObject(value, id, `migration operation ${index} must be an object.`);
634
- const type = Reflect.get(record, "type");
635
- const collection = nonEmpty(Reflect.get(record, "collection"), id, `operation ${index}.collection`);
636
- if (type === "delete-collection")
637
- return { collection, type };
638
- if (type === "rename-field")
639
- return {
640
- collection,
641
- from: nonEmpty(Reflect.get(record, "from"), id, `operation ${index}.from`),
642
- to: nonEmpty(Reflect.get(record, "to"), id, `operation ${index}.to`),
643
- type
644
- };
645
- const field2 = nonEmpty(Reflect.get(record, "field"), id, `operation ${index}.field`);
646
- if (type === "remove-field")
647
- return { collection, field: field2, type };
648
- if (type === "set-default")
649
- return {
650
- collection,
651
- field: field2,
652
- type,
653
- value: normalizeJsonValue2(Reflect.get(record, "value"), id, `operation ${index}.value`)
654
- };
655
- throw metadataError(id, `operation ${index}.type is not supported.`);
656
- }, migration = (value, id, index) => {
657
- const record = requireObject(value, id, `migration ${index} must be an object.`);
658
- const allowed = new Set(["operations", "toVersion"]);
659
- const unsupported = Object.keys(record).find((key) => !allowed.has(key));
660
- if (unsupported)
661
- throw metadataError(id, `migration ${index}.${unsupported} is not declarative metadata.`);
662
- const declaredOperations = Reflect.get(record, "operations");
663
- if (declaredOperations !== undefined && !Array.isArray(declaredOperations))
664
- throw metadataError(id, `migration ${index}.operations must be an array.`);
665
- const operations = Array.isArray(declaredOperations) ? declaredOperations : [];
505
+ }, absoluteDeviceNativeRequirements = (plan) => {
506
+ const privacy = plan.capabilities.reduce((requirements, name) => {
507
+ for (const api of IOS_PRIVACY_ACCESSED_APIS) {
508
+ const reasons = plan.providers[name]?.native?.ios?.privacyAccessedApis?.[api] ?? [];
509
+ if (reasons.length === 0)
510
+ continue;
511
+ const current = requirements[api] ?? new Set;
512
+ for (const reason of reasons)
513
+ current.add(reason);
514
+ requirements[api] = current;
515
+ }
516
+ return requirements;
517
+ }, {});
666
518
  return {
667
- operations: operations.map((entry, operationIndex) => operation(entry, id, operationIndex)),
668
- toVersion: positiveVersion2(Reflect.get(record, "toVersion"), id, `migration ${index}.toVersion`)
519
+ androidPermissions: [
520
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
521
+ ].sort(),
522
+ iosPrivacyAccessedApis: IOS_PRIVACY_ACCESSED_APIS.flatMap((api) => {
523
+ const reasons = privacy[api];
524
+ return reasons ? [{ api, reasons: [...reasons].sort() }] : [];
525
+ }),
526
+ iosPushNotifications: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.pushNotifications === true),
527
+ iosSystemBars: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.systemBars === true),
528
+ iosUsageDescriptions: [
529
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
530
+ ].sort()
669
531
  };
670
- }, localDataPolicy = (value, id) => {
671
- const record = requireObject(value, id, "localData must be an object.");
672
- const allowed = new Set([
673
- "collections",
674
- "maxBytesPerNamespace",
675
- "mutations"
676
- ]);
677
- const unsupported = Object.keys(record).find((key) => !allowed.has(key));
678
- if (unsupported)
679
- throw metadataError(id, `localData.${unsupported} is not supported.`);
680
- const collectionRules = Reflect.get(record, "collections");
681
- const mutationRules = Reflect.get(record, "mutations");
682
- if (collectionRules !== undefined && !Array.isArray(collectionRules))
683
- throw metadataError(id, "localData.collections must be an array.");
684
- if (mutationRules !== undefined && !Array.isArray(mutationRules))
685
- throw metadataError(id, "localData.mutations must be an array.");
686
- const collections = Array.isArray(collectionRules) ? collectionRules.map((entry, index) => {
687
- const rule = requireObject(entry, id, `localData.collections[${index}] must be an object.`);
688
- const allowedRuleKeys = new Set([
689
- "evictionPriority",
690
- "match",
691
- "maxAgeMs",
692
- "onProtectionUnavailable",
693
- "persistence",
694
- "protection",
695
- "sensitivity"
696
- ]);
697
- const unsupportedRuleKey = Object.keys(rule).find((key) => !allowedRuleKeys.has(key));
698
- if (unsupportedRuleKey)
699
- throw metadataError(id, `localData.collections[${index}].${unsupportedRuleKey} is not supported.`);
700
- const match = nonEmpty(Reflect.get(rule, "match"), id, `localData.collections[${index}].match`);
701
- const persistence = unknownField(rule, "persistence");
702
- const sensitivity = unknownField(rule, "sensitivity");
703
- const protection = unknownField(rule, "protection");
704
- const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
705
- const evictionPriority = unknownField(rule, "evictionPriority");
706
- const maxAge = unknownField(rule, "maxAgeMs");
707
- if (persistence !== undefined && persistence !== "durable" && persistence !== "memory-only")
708
- throw metadataError(id, `localData.collections[${index}].persistence is invalid.`);
709
- if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
710
- throw metadataError(id, `localData.collections[${index}].sensitivity is invalid.`);
711
- if (protection !== undefined && protection !== "none" && protection !== "required")
712
- throw metadataError(id, `localData.collections[${index}].protection is invalid.`);
713
- if (onProtectionUnavailable !== undefined && onProtectionUnavailable !== "error" && onProtectionUnavailable !== "memory-only")
714
- throw metadataError(id, `localData.collections[${index}].onProtectionUnavailable is invalid.`);
715
- if (evictionPriority !== undefined && evictionPriority !== "critical" && evictionPriority !== "normal" && evictionPriority !== "disposable")
716
- throw metadataError(id, `localData.collections[${index}].evictionPriority is invalid.`);
717
- return {
718
- match,
719
- ...sensitivity ? { sensitivity } : {},
720
- ...persistence ? { persistence } : {},
721
- ...protection ? { protection } : {},
722
- ...onProtectionUnavailable ? {
723
- onProtectionUnavailable
724
- } : {},
725
- ...evictionPriority ? { evictionPriority } : {},
726
- ...maxAge === undefined ? {} : {
727
- maxAgeMs: positiveVersion2(maxAge, id, `localData.collections[${index}].maxAgeMs`)
532
+ }, loadAbsoluteDeviceCapabilityProviders = (projectRoot, provider = "capacitor") => {
533
+ const adapter = ADAPTERS[provider];
534
+ let path = join11(resolve10(projectRoot), "node_modules", adapter, "package.json");
535
+ try {
536
+ readFileSync2(path, "utf8");
537
+ } catch {
538
+ path = fileURLToPath(import.meta.resolve(`${adapter}/package.json`));
539
+ }
540
+ const manifest = readJson(path);
541
+ const { absolutejs } = manifest;
542
+ const devices = object(absolutejs) ? absolutejs.devices : undefined;
543
+ if (!object(devices) || devices.format !== 1 || devices.provider !== provider || !object(devices.capabilities))
544
+ throw new TypeError(`${adapter} does not publish supported capability metadata.`);
545
+ const entries = Object.entries(devices.capabilities).map(([name, capability]) => ({
546
+ name,
547
+ provider: parseProvider(name, capability, provider)
548
+ }));
549
+ return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider: capabilityProvider }) => [
550
+ name,
551
+ capabilityProvider
552
+ ]));
553
+ }, isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment)), importedCapabilities = (source, file) => {
554
+ const names = new Set;
555
+ const namespaces = new Set;
556
+ const visit = (node) => {
557
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
558
+ const bindings = node.importClause?.namedBindings;
559
+ if (bindings && ts.isNamedImports(bindings)) {
560
+ for (const element of bindings.elements)
561
+ if (!element.isTypeOnly)
562
+ names.add((element.propertyName ?? element.name).text);
728
563
  }
729
- };
730
- }) : undefined;
731
- const mutations = Array.isArray(mutationRules) ? mutationRules.map((entry, index) => {
732
- const rule = requireObject(entry, id, `localData.mutations[${index}] must be an object.`);
733
- const allowedRuleKeys = new Set([
734
- "conflict",
735
- "match",
736
- "onProtectionUnavailable",
737
- "persistence",
738
- "protection",
739
- "sensitivity"
740
- ]);
741
- const unsupportedRuleKey = Object.keys(rule).find((key) => !allowedRuleKeys.has(key));
742
- if (unsupportedRuleKey)
743
- throw metadataError(id, `localData.mutations[${index}].${unsupportedRuleKey} is not supported.`);
744
- const protection = unknownField(rule, "protection");
745
- const sensitivity = unknownField(rule, "sensitivity");
746
- const persistence = unknownField(rule, "persistence");
747
- const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
748
- const declaredConflict = unknownField(rule, "conflict");
749
- let conflict;
750
- if (declaredConflict !== undefined) {
751
- const conflictRecord = requireObject(declaredConflict, id, `localData.mutations[${index}].conflict must be an object.`);
752
- const unsupportedConflictKey = Object.keys(conflictRecord).find((key) => key !== "maxAttempts" && key !== "strategy");
753
- if (unsupportedConflictKey)
754
- throw metadataError(id, `localData.mutations[${index}].conflict.${unsupportedConflictKey} is not supported.`);
755
- const strategy = unknownField(conflictRecord, "strategy");
756
- if (strategy !== "client-wins" && strategy !== "manual" && strategy !== "server-wins")
757
- throw metadataError(id, `localData.mutations[${index}].conflict.strategy is invalid.`);
758
- const maxAttempts = unknownField(conflictRecord, "maxAttempts");
759
- if (maxAttempts !== undefined && strategy !== "client-wins")
760
- throw metadataError(id, `localData.mutations[${index}].conflict.maxAttempts requires client-wins.`);
761
- conflict = {
762
- strategy,
763
- ...maxAttempts === undefined ? {} : {
764
- maxAttempts: positiveVersion2(maxAttempts, id, `localData.mutations[${index}].conflict.maxAttempts`)
765
- }
766
- };
564
+ if (bindings && ts.isNamespaceImport(bindings))
565
+ namespaces.add(bindings.name.text);
767
566
  }
768
- if (protection !== undefined && protection !== "none" && protection !== "required")
769
- throw metadataError(id, `localData.mutations[${index}].protection is invalid.`);
770
- if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
771
- throw metadataError(id, `localData.mutations[${index}].sensitivity is invalid.`);
772
- if (onProtectionUnavailable !== undefined && onProtectionUnavailable !== "error" && onProtectionUnavailable !== "memory-only")
773
- throw metadataError(id, `localData.mutations[${index}].onProtectionUnavailable is invalid.`);
774
- if (persistence !== undefined && persistence !== "durable" && persistence !== "memory-only")
775
- throw metadataError(id, `localData.mutations[${index}].persistence is invalid.`);
776
- return {
777
- match: nonEmpty(Reflect.get(rule, "match"), id, `localData.mutations[${index}].match`),
778
- ...conflict ? { conflict } : {},
779
- ...sensitivity ? { sensitivity } : {},
780
- ...onProtectionUnavailable ? { onProtectionUnavailable } : {},
781
- ...persistence ? {
782
- persistence
783
- } : {},
784
- ...protection ? { protection } : {}
785
- };
786
- }) : undefined;
787
- const quota = Reflect.get(record, "maxBytesPerNamespace");
788
- return {
789
- ...collections ? { collections } : {},
790
- ...mutations ? { mutations } : {},
791
- ...quota === undefined ? {} : {
792
- maxBytesPerNamespace: positiveVersion2(quota, id, "localData.maxBytesPerNamespace")
567
+ if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
568
+ for (const element of node.exportClause.elements)
569
+ if (!element.isTypeOnly)
570
+ names.add((element.propertyName ?? element.name).text);
571
+ }
572
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
573
+ names.add(node.name.text);
574
+ ts.forEachChild(node, visit);
575
+ };
576
+ const extension = extname3(file).toLowerCase();
577
+ const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
578
+ for (const [index, script] of sources.entries())
579
+ visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
580
+ return names;
581
+ }, assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
582
+ const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
583
+ const mismatched = plan.requiredPackages.filter((spec) => {
584
+ const separator = spec.lastIndexOf("@");
585
+ const packageName = spec.slice(0, separator);
586
+ if (missing.includes(spec))
587
+ return false;
588
+ try {
589
+ return readJson(join11(resolve10(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
590
+ } catch {
591
+ return true;
793
592
  }
593
+ });
594
+ const unmet = [...missing, ...mismatched];
595
+ if (unmet.length > 0)
596
+ throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
597
+ }, directAbsoluteProjectPackages = (projectRoot) => {
598
+ const manifest = readJson(join11(resolve10(projectRoot), "package.json"));
599
+ const packages = new Set;
600
+ for (const field2 of ["dependencies", "devDependencies"]) {
601
+ const dependencies = manifest[field2];
602
+ if (object(dependencies))
603
+ for (const name of Object.keys(dependencies))
604
+ packages.add(name);
605
+ }
606
+ return packages;
607
+ }, discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
608
+ const root = resolve10(projectRoot);
609
+ if (!existsSync3(root))
610
+ return [];
611
+ const known = new Set(Object.keys(providers));
612
+ const capabilities = new Set;
613
+ for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
614
+ const portable = relative9(root, resolve10(root, path)).replaceAll("\\", "/");
615
+ if (isIgnored(portable))
616
+ continue;
617
+ const source = readFileSync2(resolve10(root, portable), "utf8");
618
+ for (const name of importedCapabilities(source, portable))
619
+ if (known.has(name))
620
+ capabilities.add(name);
621
+ }
622
+ return [...capabilities].sort();
623
+ }, missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
624
+ const packageName = spec.slice(0, spec.lastIndexOf("@"));
625
+ return !directPackages.has(packageName);
626
+ }), projectImportsAbsoluteDeviceCapability = (projectRoot, capability) => {
627
+ const root = resolve10(projectRoot);
628
+ for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
629
+ const portable = relative9(root, resolve10(root, path)).replaceAll("\\", "/");
630
+ if (isIgnored(portable))
631
+ continue;
632
+ const source = readFileSync2(resolve10(root, portable), "utf8");
633
+ if (importedCapabilities(source, portable).has(capability))
634
+ return true;
635
+ }
636
+ return false;
637
+ }, resolveAbsoluteDeviceCapabilityPlan = (projectRoot, provider = "capacitor") => {
638
+ const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot, provider);
639
+ const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
640
+ for (const capability of ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES) {
641
+ if (allProviders[capability] && !capabilities.includes(capability))
642
+ capabilities.push(capability);
643
+ }
644
+ capabilities.sort();
645
+ const providers = {};
646
+ for (const name of capabilities) {
647
+ const capabilityProvider = allProviders[name];
648
+ if (capabilityProvider)
649
+ providers[name] = capabilityProvider;
650
+ }
651
+ return {
652
+ capabilities,
653
+ providers,
654
+ requiredPackages: [
655
+ ...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
656
+ ].sort()
794
657
  };
795
- }, component = (id, value) => {
796
- const record = requireObject(value, id, "localSchema must be an object.");
797
- const allowed = new Set([
798
- "localData",
799
- "migrations",
800
- "minimumCompatibleVersion",
801
- "version"
658
+ };
659
+ var init_deviceCapabilities = __esm(() => {
660
+ ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES = [
661
+ "keyboard",
662
+ "systemBars"
663
+ ];
664
+ ADAPTERS = {
665
+ capacitor: "@absolutejs/devices-capacitor",
666
+ expo: "@absolutejs/devices-expo"
667
+ };
668
+ SOURCE_GLOB = new Bun.Glob("**/*.{js,jsx,ts,tsx,svelte,vue}");
669
+ IGNORED_DIRECTORIES = new Set([
670
+ ".absolutejs",
671
+ ".git",
672
+ ".test-builds",
673
+ ".test-shards",
674
+ "build",
675
+ "dist",
676
+ "node_modules",
677
+ "test",
678
+ "tests"
802
679
  ]);
803
- const unsupported = Object.keys(record).find((key) => !allowed.has(key));
804
- if (unsupported)
805
- throw metadataError(id, `${unsupported} is not supported.`);
806
- const version = positiveVersion2(Reflect.get(record, "version"), id, "version");
807
- const declaredMinimum = Reflect.get(record, "minimumCompatibleVersion");
808
- const minimumCompatibleVersion = declaredMinimum === undefined ? Math.max(1, version - 2) : positiveVersion2(declaredMinimum, id, "minimumCompatibleVersion");
809
- const declaredMigrations = Reflect.get(record, "migrations");
810
- const declaredLocalData = Reflect.get(record, "localData");
811
- if (declaredMigrations !== undefined && !Array.isArray(declaredMigrations))
812
- throw metadataError(id, "migrations must be an array.");
813
- const migrations = Array.isArray(declaredMigrations) ? declaredMigrations : undefined;
680
+ IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
681
+ ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
682
+ IOS_USAGE_DESCRIPTIONS = new Set([
683
+ "camera",
684
+ "location-always",
685
+ "location-when-in-use",
686
+ "photo-library",
687
+ "photo-library-add"
688
+ ]);
689
+ IOS_PRIVACY_ACCESSED_API_REASONS = {
690
+ NSPrivacyAccessedAPICategoryFileTimestamp: new Set(["C617.1"])
691
+ };
692
+ IOS_PRIVACY_ACCESSED_APIS = [
693
+ "NSPrivacyAccessedAPICategoryFileTimestamp"
694
+ ];
695
+ });
696
+
697
+ // src/mobile/nativeAuth.ts
698
+ import { readFileSync as readFileSync3 } from "fs";
699
+ import { join as join12 } from "path";
700
+ var ABSOLUTE_AUTH_PACKAGE = "@absolutejs/auth", ABSOLUTE_EXPO_AUTH_CORE_VERSION = "0.75.6", ABSOLUTE_EXPO_AUTH_PACKAGE = "@absolutejs/auth-expo", ABSOLUTE_EXPO_AUTH_VERSION = "0.0.2", ABSOLUTE_EXPO_SYNC_CORE_VERSION = "2.31.0", ABSOLUTE_EXPO_SYNC_PACKAGE = "@absolutejs/sync-expo", ABSOLUTE_EXPO_SYNC_VERSION = "0.0.2", ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV = "ABSOLUTE_AUTH_NATIVE_CLIENTS", ABSOLUTE_NATIVE_AUTH_SCOPES, ABSOLUTE_SYNC_PACKAGE = "@absolutejs/sync", readPackageManifest = (projectRoot) => {
701
+ try {
702
+ return JSON.parse(readFileSync3(join12(projectRoot, "package.json"), "utf8"));
703
+ } catch {
704
+ return;
705
+ }
706
+ }, packageManifestHas = (manifest, packageName) => {
707
+ if (typeof manifest !== "object" || manifest === null)
708
+ return false;
709
+ return [
710
+ Reflect.get(manifest, "dependencies"),
711
+ Reflect.get(manifest, "devDependencies"),
712
+ Reflect.get(manifest, "optionalDependencies"),
713
+ Reflect.get(manifest, "peerDependencies")
714
+ ].some((dependencies) => typeof dependencies === "object" && dependencies !== null && Object.hasOwn(dependencies, packageName));
715
+ }, createAbsoluteMobileAuthManifest = (config) => {
716
+ const scheme = config.deepLinkScheme ?? config.appId.toLowerCase();
814
717
  return {
815
- id,
816
- ...declaredLocalData === undefined ? {} : { localData: localDataPolicy(declaredLocalData, id) },
817
- minimumCompatibleVersion,
818
- ...Array.isArray(migrations) ? {
819
- migrations: migrations.map((entry, index) => migration(entry, id, index))
820
- } : {},
821
- version
718
+ clientId: `absolutejs-native:${config.appId}`,
719
+ issuer: config.productionOrigin,
720
+ redirectUri: `${scheme}://auth/callback`,
721
+ scopes: [...ABSOLUTE_NATIVE_AUTH_SCOPES]
822
722
  };
823
- }, dependencyNames = (manifest) => [
824
- Reflect.get(manifest, "dependencies"),
825
- Reflect.get(manifest, "optionalDependencies"),
826
- Reflect.get(manifest, "devDependencies"),
827
- Reflect.get(manifest, "peerDependencies")
828
- ].flatMap((dependencies) => object(dependencies) ? Object.keys(dependencies) : []).filter((name, index, names) => names.indexOf(name) === index).sort(), discoverAbsoluteSyncSchema = (projectRoot) => {
829
- const appManifestPath = join12(resolve10(projectRoot), "package.json");
830
- const appManifest = manifestAt(appManifestPath);
831
- if (!appManifest)
723
+ }, installAbsoluteMobileAuthEnvironment = (projectRoot, config) => {
724
+ const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
725
+ const serialized = serializeAbsoluteMobileAuthEnvironment(config, auth);
726
+ if (serialized === undefined)
727
+ delete process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV];
728
+ else
729
+ process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV] = serialized;
730
+ return auth;
731
+ }, projectUsesAbsoluteAuth = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_AUTH_PACKAGE), projectUsesAbsoluteSync = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_SYNC_PACKAGE), resolveAbsoluteMobileAuthManifest = (projectRoot, config) => projectUsesAbsoluteAuth(projectRoot) ? createAbsoluteMobileAuthManifest(config) : undefined, serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefined ? undefined : JSON.stringify([
732
+ {
733
+ ...auth,
734
+ name: `${config.appName} native app`
735
+ }
736
+ ]);
737
+ var init_nativeAuth = __esm(() => {
738
+ ABSOLUTE_NATIVE_AUTH_SCOPES = ["openid", "profile"];
739
+ });
740
+
741
+ // node_modules/@absolutejs/sync/dist/client/index.js
742
+ var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients")), registry, pools, SyncLocalDataPolicyError, SyncLocalStoreSchemaError, positiveVersion = (value, label) => {
743
+ if (!Number.isSafeInteger(value) || value < 1)
744
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", `${label} must be a positive safe integer`);
745
+ return value;
746
+ }, isSchemaBundle = (schema) => ("components" in schema), validatePolicyMatch = (match, label) => {
747
+ if (match.length === 0 || match.trim() !== match || /^\*+$/.test(match) || match.includes("**"))
748
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.match must be an exact name or a non-empty glob without adjacent wildcards.`);
749
+ }, validateSyncLocalDataPolicy = (policy, label = "localData") => {
750
+ if (policy.maxBytesPerNamespace !== undefined && (!Number.isSafeInteger(policy.maxBytesPerNamespace) || policy.maxBytesPerNamespace < 1))
751
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.maxBytesPerNamespace must be a positive safe integer.`);
752
+ for (const [index, rule] of (policy.collections ?? []).entries()) {
753
+ validatePolicyMatch(rule.match, `${label}.collections[${index}]`);
754
+ if (rule.maxAgeMs !== undefined && (!Number.isSafeInteger(rule.maxAgeMs) || rule.maxAgeMs < 1))
755
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}].maxAgeMs must be a positive safe integer.`);
756
+ if (rule.persistence === "memory-only" && rule.protection === "required")
757
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}] cannot require at-rest protection when it is memory-only.`);
758
+ if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
759
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}] declares ${rule.sensitivity} data without required protection or memory-only persistence.`);
760
+ }
761
+ for (const [index, rule] of (policy.mutations ?? []).entries()) {
762
+ validatePolicyMatch(rule.match, `${label}.mutations[${index}]`);
763
+ if (rule.conflict !== undefined && rule.conflict.strategy !== "client-wins" && rule.conflict.strategy !== "manual" && rule.conflict.strategy !== "server-wins")
764
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.strategy is invalid.`);
765
+ if (rule.conflict?.maxAttempts !== undefined && (!Number.isSafeInteger(rule.conflict.maxAttempts) || rule.conflict.maxAttempts < 1))
766
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts must be a positive safe integer.`);
767
+ if (rule.conflict?.maxAttempts !== undefined && rule.conflict.strategy !== "client-wins")
768
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts is only valid for client-wins.`);
769
+ if (rule.persistence === "memory-only" && rule.protection === "required")
770
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] cannot require at-rest protection when it is memory-only.`);
771
+ if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
772
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] declares ${rule.sensitivity} arguments without required protection.`);
773
+ }
774
+ return policy;
775
+ }, normalizeSyncLocalSchemaComponents = (schema = { version: 1 }) => {
776
+ const components = isSchemaBundle(schema) ? [...schema.components] : [{ ...schema, id: "@absolutejs/app" }];
777
+ const ids = new Set;
778
+ for (const component of components) {
779
+ if (typeof component.id !== "string" || component.id.trim() !== component.id || component.id.length === 0)
780
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", "Sync schema component id must be non-empty and trimmed");
781
+ if (ids.has(component.id))
782
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", `Sync schema component "${component.id}" is declared more than once`);
783
+ ids.add(component.id);
784
+ if (component.localData)
785
+ validateSyncLocalDataPolicy(component.localData, `${component.id}.localData`);
786
+ }
787
+ return components.sort((a, b) => a.id.localeCompare(b.id));
788
+ }, resolveSyncLocalSchemaComponents = (storedVersions, schema = { version: 1 }) => {
789
+ const components = normalizeSyncLocalSchemaComponents(schema).map((component) => {
790
+ const current = resolveSyncLocalMigrations(component.version, component);
832
791
  return {
833
- components: [
834
- {
835
- id: "@absolutejs/app",
836
- minimumCompatibleVersion: 1,
837
- version: 1
838
- }
839
- ],
840
- sources: []
841
- };
842
- const appMetadata = localSchemaMetadata(appManifest);
843
- const components = [
844
- appMetadata === undefined ? { id: "@absolutejs/app", minimumCompatibleVersion: 1, version: 1 } : component("@absolutejs/app", appMetadata)
845
- ];
846
- const sources = [
847
- { id: "@absolutejs/app", manifestPath: appManifestPath }
848
- ];
849
- for (const name of dependencyNames(appManifest)) {
850
- const manifestPath = packageManifestPath(projectRoot, name);
851
- if (!manifestPath)
852
- continue;
853
- const manifest = manifestAt(manifestPath);
854
- if (!manifest)
855
- continue;
856
- const metadata = localSchemaMetadata(manifest);
857
- if (metadata === undefined)
858
- continue;
859
- components.push(component(name, metadata));
860
- sources.push({ id: name, manifestPath });
792
+ id: component.id,
793
+ ...resolveSyncLocalMigrations(storedVersions[component.id] ?? current.minimumCompatibleVersion, component)
794
+ };
795
+ });
796
+ const active = new Set(components.map((component) => component.id));
797
+ const orphanedComponents = Object.keys(storedVersions).filter((id) => !active.has(id)).sort();
798
+ return { components, orphanedComponents };
799
+ }, resolveSyncLocalMigrations = (storedVersion, schema = { version: 1 }) => {
800
+ positiveVersion(storedVersion, "Stored Sync schema version");
801
+ const targetVersion = positiveVersion(schema.version, "Target Sync schema version");
802
+ const migrations = [...schema.migrations ?? []].sort((a, b) => a.toVersion - b.toVersion);
803
+ const versions = new Set;
804
+ for (const migration of migrations) {
805
+ positiveVersion(migration.toVersion, "Sync migration toVersion");
806
+ if (versions.has(migration.toVersion))
807
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", `Sync migration ${migration.toVersion} is declared more than once`);
808
+ versions.add(migration.toVersion);
861
809
  }
862
- components.sort((left, right) => left.id.localeCompare(right.id));
863
- sources.sort((left, right) => left.id.localeCompare(right.id));
864
- resolveSyncLocalSchemaComponents({}, { components });
865
- return { components, sources };
810
+ const inferredMinimum = migrations[0] ? migrations[0].toVersion - 1 : targetVersion;
811
+ const minimumCompatibleVersion = positiveVersion(schema.minimumCompatibleVersion ?? inferredMinimum, "Minimum compatible Sync schema version");
812
+ if (minimumCompatibleVersion > targetVersion)
813
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", "Minimum compatible Sync schema version cannot exceed its target");
814
+ if (storedVersion > targetVersion)
815
+ throw new SyncLocalStoreSchemaError("SCHEMA_TOO_NEW", `Stored Sync schema ${storedVersion} is newer than this runtime's schema ${targetVersion}`, { storedVersion, targetVersion });
816
+ if (storedVersion < minimumCompatibleVersion)
817
+ throw new SyncLocalStoreSchemaError("SCHEMA_TOO_OLD", `Stored Sync schema ${storedVersion} is older than the minimum compatible schema ${minimumCompatibleVersion}`, { storedVersion, targetVersion });
818
+ const steps = [];
819
+ for (let version = storedVersion + 1;version <= targetVersion; version++) {
820
+ const migration = migrations.find((candidate) => candidate.toVersion === version);
821
+ if (migration === undefined)
822
+ throw new SyncLocalStoreSchemaError("MIGRATION_MISSING", `Sync migration ${version - 1} -> ${version} is missing`, { storedVersion, targetVersion });
823
+ steps.push(migration);
824
+ }
825
+ return { minimumCompatibleVersion, steps, targetVersion };
866
826
  };
867
- var init_syncSchema = __esm(() => {
868
- init_client();
827
+ var init_client = __esm(() => {
828
+ RUNTIME_TRANSPORT = Symbol.for("@absolutejs/sync/client-runtime-transport");
829
+ host = globalThis;
830
+ registry = (() => {
831
+ const existing = host[RUNTIME_TRANSPORT];
832
+ if (isRegistry(existing))
833
+ return existing;
834
+ if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
835
+ Reflect.set(existing, "clients", []);
836
+ return existing;
837
+ }
838
+ const created = { clients: [], installations: [] };
839
+ Object.defineProperty(host, RUNTIME_TRANSPORT, {
840
+ configurable: false,
841
+ enumerable: false,
842
+ value: created,
843
+ writable: false
844
+ });
845
+ return created;
846
+ })();
847
+ pools = new Map;
848
+ SyncLocalDataPolicyError = class SyncLocalDataPolicyError extends Error {
849
+ code;
850
+ constructor(code, message) {
851
+ super(message);
852
+ this.name = "SyncLocalDataPolicyError";
853
+ this.code = code;
854
+ }
855
+ };
856
+ SyncLocalStoreSchemaError = class SyncLocalStoreSchemaError extends Error {
857
+ code;
858
+ storedVersion;
859
+ targetVersion;
860
+ constructor(code, message, versions = {}) {
861
+ super(message);
862
+ this.name = "SyncLocalStoreSchemaError";
863
+ this.code = code;
864
+ this.storedVersion = versions.storedVersion;
865
+ this.targetVersion = versions.targetVersion;
866
+ }
867
+ };
869
868
  });
870
869
 
871
- // src/mobile/deviceCapabilities.ts
872
- import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
873
- import { extname as extname3, join as join13, relative as relative9, resolve as resolve11 } from "path";
874
- import { fileURLToPath } from "url";
875
- import ts from "typescript";
876
- var DEVICES_PACKAGE = "@absolutejs/devices", ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES, ADAPTERS, SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, providerModulePattern = (provider) => new RegExp(`^@absolutejs/devices-${provider}/[a-z][a-z0-9-]*$`, "u"), providerPackagePattern = (provider) => provider === "capacitor" ? /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u : /^(?:expo-[a-z][a-z0-9-]*|@react-native-[a-z0-9-]+\/[a-z][a-z0-9-]*)@\d+\.\d+\.\d+$/u, providerLabel = (provider) => provider === "capacitor" ? "Capacitor" : "Expo", ANDROID_PERMISSION_PATTERN, IOS_USAGE_DESCRIPTIONS, IOS_PRIVACY_ACCESSED_API_REASONS, IOS_PRIVACY_ACCESSED_APIS, object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson = (path) => {
877
- const value = JSON.parse(readFileSync4(path, "utf8"));
878
- if (!object2(value))
879
- throw new TypeError(`${path} must contain an object.`);
880
- return value;
881
- }, text = (value, field2) => {
882
- if (typeof value !== "string" || value.length === 0)
883
- throw new TypeError(`${field2} must be a non-empty string.`);
884
- return value;
885
- }, androidPermissions = (value, field2) => {
886
- if (value === undefined)
887
- return;
888
- if (!object2(value))
889
- throw new TypeError(`${field2} must be an object.`);
890
- const { permissions } = value;
891
- if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
892
- throw new TypeError(`${field2}.permissions must contain Android permission names.`);
893
- return [...permissions];
894
- }, iosPrivacyAccessedApis = (value, field2) => {
895
- if (value === undefined)
870
+ // src/mobile/syncSchema.ts
871
+ import { readFileSync as readFileSync4 } from "fs";
872
+ import { dirname as dirname9, join as join13, resolve as resolve11 } from "path";
873
+ var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), manifestAt = (path) => {
874
+ try {
875
+ const value = JSON.parse(readFileSync4(path, "utf8"));
876
+ return object2(value) ? value : undefined;
877
+ } catch {
896
878
  return;
897
- if (!object2(value))
898
- throw new TypeError(`${field2} must be an object.`);
899
- const privacy = {};
900
- for (const api of IOS_PRIVACY_ACCESSED_APIS) {
901
- const reasons = value[api];
902
- if (reasons === undefined)
903
- continue;
904
- const supported = IOS_PRIVACY_ACCESSED_API_REASONS[api];
905
- if (!Array.isArray(reasons) || reasons.length === 0 || !reasons.every((reason) => typeof reason === "string" && supported.has(reason)))
906
- throw new TypeError(`${field2} contains an unsupported API or reason.`);
907
- privacy[api] = [...reasons];
908
879
  }
909
- if (Object.keys(value).some((api) => !IOS_PRIVACY_ACCESSED_APIS.some((known) => known === api)))
910
- throw new TypeError(`${field2} contains an unsupported API or reason.`);
911
- return privacy;
912
- }, iosNativeRequirements = (value, field2) => {
913
- if (value === undefined)
880
+ }, localSchemaMetadata = (manifest) => {
881
+ const absolutejs = Reflect.get(manifest, "absolutejs");
882
+ if (!object2(absolutejs))
914
883
  return;
884
+ const sync = Reflect.get(absolutejs, "sync");
885
+ if (!object2(sync))
886
+ return;
887
+ return Reflect.get(sync, "localSchema");
888
+ }, packageManifestPath = (projectRoot, packageName) => {
889
+ let directory = resolve11(projectRoot);
890
+ while (true) {
891
+ const candidate = join13(directory, "node_modules", packageName, "package.json");
892
+ const manifest = manifestAt(candidate);
893
+ if (manifest && Reflect.get(manifest, "name") === packageName)
894
+ return candidate;
895
+ const parent = dirname9(directory);
896
+ if (parent === directory)
897
+ return;
898
+ directory = parent;
899
+ }
900
+ }, metadataError = (id, detail) => new TypeError(`Invalid AbsoluteJS Sync schema metadata for ${id}: ${detail}`), positiveVersion2 = (value, id, field2) => {
901
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
902
+ throw metadataError(id, `${field2} must be a positive safe integer.`);
903
+ return value;
904
+ }, nonEmpty = (value, id, field2) => {
905
+ if (typeof value !== "string" || value.trim() !== value || value.length === 0)
906
+ throw metadataError(id, `${field2} must be a non-empty trimmed string.`);
907
+ return value;
908
+ }, requireObject = (value, id, detail) => {
915
909
  if (!object2(value))
916
- throw new TypeError(`${field2} must be an object.`);
917
- const {
918
- privacyAccessedApis,
919
- pushNotifications,
920
- systemBars: systemBars2,
921
- usageDescriptions
922
- } = value;
923
- if (pushNotifications !== undefined && pushNotifications !== true)
924
- throw new TypeError(`${field2}.pushNotifications must be true.`);
925
- if (systemBars2 !== undefined && systemBars2 !== true)
926
- throw new TypeError(`${field2}.systemBars must be true.`);
927
- if (usageDescriptions !== undefined && (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose))))
928
- throw new TypeError(`${field2}.usageDescriptions contains an unsupported purpose.`);
929
- const privacy = iosPrivacyAccessedApis(privacyAccessedApis, `${field2}.privacyAccessedApis`);
930
- return {
931
- ...privacy === undefined ? {} : { privacyAccessedApis: privacy },
932
- ...pushNotifications === true ? { pushNotifications: true } : {},
933
- ...systemBars2 === true ? { systemBars: true } : {},
934
- ...usageDescriptions === undefined ? {} : { usageDescriptions: [...usageDescriptions] }
935
- };
936
- }, parseProvider = (name, value, providerName) => {
937
- if (!IDENTIFIER_PATTERN.test(name))
938
- throw new TypeError("Device capability names must be identifiers.");
939
- if (!object2(value))
940
- throw new TypeError(`Device capability ${name} must be an object.`);
941
- const factory = text(value.factory, `${name}.factory`);
942
- const module = text(value.module, `${name}.module`);
943
- if (!IDENTIFIER_PATTERN.test(factory))
944
- throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
945
- if (!providerModulePattern(providerName).test(module))
946
- throw new TypeError(`${name}.module must be an official devices-${providerName} subpath.`);
947
- if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && providerPackagePattern(providerName).test(spec)))
948
- throw new TypeError(`${name}.packages must contain exact official ${providerLabel(providerName)} package versions.`);
949
- const { plugins } = value;
950
- if (plugins !== undefined && (!Array.isArray(plugins) || !plugins.every((plugin) => typeof plugin === "string" && /^expo-[a-z][a-z0-9-]*$/u.test(plugin))))
951
- throw new TypeError(`${name}.plugins must contain Expo config plugin names.`);
952
- let native;
953
- const { native: nativeMetadata } = value;
954
- if (nativeMetadata !== undefined) {
955
- if (!object2(nativeMetadata))
956
- throw new TypeError(`${name}.native must be an object.`);
957
- const { android, ios } = nativeMetadata;
958
- const permissions = androidPermissions(android, `${name}.native.android`);
959
- const iosRequirements = iosNativeRequirements(ios, `${name}.native.ios`);
960
- native = {
961
- ...permissions === undefined ? {} : { android: { permissions } },
962
- ...iosRequirements === undefined ? {} : { ios: iosRequirements }
910
+ throw metadataError(id, detail);
911
+ return value;
912
+ }, unknownField = (record, key) => record[key], normalizeJsonValue2 = (value, id, field2) => {
913
+ if (value === null || typeof value === "string" || typeof value === "boolean")
914
+ return value;
915
+ if (typeof value === "number" && Number.isFinite(value))
916
+ return value;
917
+ if (Array.isArray(value))
918
+ return value.map((entry) => normalizeJsonValue2(entry, id, field2));
919
+ if (object2(value))
920
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
921
+ key,
922
+ normalizeJsonValue2(entry, id, field2)
923
+ ]));
924
+ throw metadataError(id, `${field2} must be JSON-safe.`);
925
+ }, operation = (value, id, index) => {
926
+ const record = requireObject(value, id, `migration operation ${index} must be an object.`);
927
+ const type = Reflect.get(record, "type");
928
+ const collection = nonEmpty(Reflect.get(record, "collection"), id, `operation ${index}.collection`);
929
+ if (type === "delete-collection")
930
+ return { collection, type };
931
+ if (type === "rename-field")
932
+ return {
933
+ collection,
934
+ from: nonEmpty(Reflect.get(record, "from"), id, `operation ${index}.from`),
935
+ to: nonEmpty(Reflect.get(record, "to"), id, `operation ${index}.to`),
936
+ type
963
937
  };
964
- }
965
- return {
966
- factory,
967
- module,
968
- ...native === undefined ? {} : { native },
969
- ...plugins === undefined ? {} : { plugins: [...plugins] },
970
- packages: [...value.packages]
971
- };
972
- }, absoluteDeviceNativeRequirements = (plan) => {
973
- const privacy = plan.capabilities.reduce((requirements, name) => {
974
- for (const api of IOS_PRIVACY_ACCESSED_APIS) {
975
- const reasons = plan.providers[name]?.native?.ios?.privacyAccessedApis?.[api] ?? [];
976
- if (reasons.length === 0)
977
- continue;
978
- const current = requirements[api] ?? new Set;
979
- for (const reason of reasons)
980
- current.add(reason);
981
- requirements[api] = current;
982
- }
983
- return requirements;
984
- }, {});
938
+ const field2 = nonEmpty(Reflect.get(record, "field"), id, `operation ${index}.field`);
939
+ if (type === "remove-field")
940
+ return { collection, field: field2, type };
941
+ if (type === "set-default")
942
+ return {
943
+ collection,
944
+ field: field2,
945
+ type,
946
+ value: normalizeJsonValue2(Reflect.get(record, "value"), id, `operation ${index}.value`)
947
+ };
948
+ throw metadataError(id, `operation ${index}.type is not supported.`);
949
+ }, migration = (value, id, index) => {
950
+ const record = requireObject(value, id, `migration ${index} must be an object.`);
951
+ const allowed = new Set(["operations", "toVersion"]);
952
+ const unsupported = Object.keys(record).find((key) => !allowed.has(key));
953
+ if (unsupported)
954
+ throw metadataError(id, `migration ${index}.${unsupported} is not declarative metadata.`);
955
+ const declaredOperations = Reflect.get(record, "operations");
956
+ if (declaredOperations !== undefined && !Array.isArray(declaredOperations))
957
+ throw metadataError(id, `migration ${index}.operations must be an array.`);
958
+ const operations = Array.isArray(declaredOperations) ? declaredOperations : [];
985
959
  return {
986
- androidPermissions: [
987
- ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
988
- ].sort(),
989
- iosPrivacyAccessedApis: IOS_PRIVACY_ACCESSED_APIS.flatMap((api) => {
990
- const reasons = privacy[api];
991
- return reasons ? [{ api, reasons: [...reasons].sort() }] : [];
992
- }),
993
- iosPushNotifications: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.pushNotifications === true),
994
- iosSystemBars: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.systemBars === true),
995
- iosUsageDescriptions: [
996
- ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
997
- ].sort()
960
+ operations: operations.map((entry, operationIndex) => operation(entry, id, operationIndex)),
961
+ toVersion: positiveVersion2(Reflect.get(record, "toVersion"), id, `migration ${index}.toVersion`)
998
962
  };
999
- }, loadAbsoluteDeviceCapabilityProviders = (projectRoot, provider = "capacitor") => {
1000
- const adapter = ADAPTERS[provider];
1001
- let path = join13(resolve11(projectRoot), "node_modules", adapter, "package.json");
1002
- try {
1003
- readFileSync4(path, "utf8");
1004
- } catch {
1005
- path = fileURLToPath(import.meta.resolve(`${adapter}/package.json`));
1006
- }
1007
- const manifest = readJson(path);
1008
- const { absolutejs } = manifest;
1009
- const devices = object2(absolutejs) ? absolutejs.devices : undefined;
1010
- if (!object2(devices) || devices.format !== 1 || devices.provider !== provider || !object2(devices.capabilities))
1011
- throw new TypeError(`${adapter} does not publish supported capability metadata.`);
1012
- const entries = Object.entries(devices.capabilities).map(([name, capability]) => ({
1013
- name,
1014
- provider: parseProvider(name, capability, provider)
1015
- }));
1016
- return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider: capabilityProvider }) => [
1017
- name,
1018
- capabilityProvider
1019
- ]));
1020
- }, isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment)), importedCapabilities = (source, file) => {
1021
- const names = new Set;
1022
- const namespaces = new Set;
1023
- const visit = (node) => {
1024
- if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
1025
- const bindings = node.importClause?.namedBindings;
1026
- if (bindings && ts.isNamedImports(bindings)) {
1027
- for (const element of bindings.elements)
1028
- if (!element.isTypeOnly)
1029
- names.add((element.propertyName ?? element.name).text);
963
+ }, localDataPolicy = (value, id) => {
964
+ const record = requireObject(value, id, "localData must be an object.");
965
+ const allowed = new Set([
966
+ "collections",
967
+ "maxBytesPerNamespace",
968
+ "mutations"
969
+ ]);
970
+ const unsupported = Object.keys(record).find((key) => !allowed.has(key));
971
+ if (unsupported)
972
+ throw metadataError(id, `localData.${unsupported} is not supported.`);
973
+ const collectionRules = Reflect.get(record, "collections");
974
+ const mutationRules = Reflect.get(record, "mutations");
975
+ if (collectionRules !== undefined && !Array.isArray(collectionRules))
976
+ throw metadataError(id, "localData.collections must be an array.");
977
+ if (mutationRules !== undefined && !Array.isArray(mutationRules))
978
+ throw metadataError(id, "localData.mutations must be an array.");
979
+ const collections = Array.isArray(collectionRules) ? collectionRules.map((entry, index) => {
980
+ const rule = requireObject(entry, id, `localData.collections[${index}] must be an object.`);
981
+ const allowedRuleKeys = new Set([
982
+ "evictionPriority",
983
+ "match",
984
+ "maxAgeMs",
985
+ "onProtectionUnavailable",
986
+ "persistence",
987
+ "protection",
988
+ "sensitivity"
989
+ ]);
990
+ const unsupportedRuleKey = Object.keys(rule).find((key) => !allowedRuleKeys.has(key));
991
+ if (unsupportedRuleKey)
992
+ throw metadataError(id, `localData.collections[${index}].${unsupportedRuleKey} is not supported.`);
993
+ const match = nonEmpty(Reflect.get(rule, "match"), id, `localData.collections[${index}].match`);
994
+ const persistence = unknownField(rule, "persistence");
995
+ const sensitivity = unknownField(rule, "sensitivity");
996
+ const protection = unknownField(rule, "protection");
997
+ const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
998
+ const evictionPriority = unknownField(rule, "evictionPriority");
999
+ const maxAge = unknownField(rule, "maxAgeMs");
1000
+ if (persistence !== undefined && persistence !== "durable" && persistence !== "memory-only")
1001
+ throw metadataError(id, `localData.collections[${index}].persistence is invalid.`);
1002
+ if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
1003
+ throw metadataError(id, `localData.collections[${index}].sensitivity is invalid.`);
1004
+ if (protection !== undefined && protection !== "none" && protection !== "required")
1005
+ throw metadataError(id, `localData.collections[${index}].protection is invalid.`);
1006
+ if (onProtectionUnavailable !== undefined && onProtectionUnavailable !== "error" && onProtectionUnavailable !== "memory-only")
1007
+ throw metadataError(id, `localData.collections[${index}].onProtectionUnavailable is invalid.`);
1008
+ if (evictionPriority !== undefined && evictionPriority !== "critical" && evictionPriority !== "normal" && evictionPriority !== "disposable")
1009
+ throw metadataError(id, `localData.collections[${index}].evictionPriority is invalid.`);
1010
+ return {
1011
+ match,
1012
+ ...sensitivity ? { sensitivity } : {},
1013
+ ...persistence ? { persistence } : {},
1014
+ ...protection ? { protection } : {},
1015
+ ...onProtectionUnavailable ? {
1016
+ onProtectionUnavailable
1017
+ } : {},
1018
+ ...evictionPriority ? { evictionPriority } : {},
1019
+ ...maxAge === undefined ? {} : {
1020
+ maxAgeMs: positiveVersion2(maxAge, id, `localData.collections[${index}].maxAgeMs`)
1030
1021
  }
1031
- if (bindings && ts.isNamespaceImport(bindings))
1032
- namespaces.add(bindings.name.text);
1033
- }
1034
- if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
1035
- for (const element of node.exportClause.elements)
1036
- if (!element.isTypeOnly)
1037
- names.add((element.propertyName ?? element.name).text);
1038
- }
1039
- if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
1040
- names.add(node.name.text);
1041
- ts.forEachChild(node, visit);
1042
- };
1043
- const extension = extname3(file).toLowerCase();
1044
- const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
1045
- for (const [index, script] of sources.entries())
1046
- visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
1047
- return names;
1048
- }, assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
1049
- const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
1050
- const mismatched = plan.requiredPackages.filter((spec) => {
1051
- const separator = spec.lastIndexOf("@");
1052
- const packageName = spec.slice(0, separator);
1053
- if (missing.includes(spec))
1054
- return false;
1055
- try {
1056
- return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
1057
- } catch {
1058
- return true;
1022
+ };
1023
+ }) : undefined;
1024
+ const mutations = Array.isArray(mutationRules) ? mutationRules.map((entry, index) => {
1025
+ const rule = requireObject(entry, id, `localData.mutations[${index}] must be an object.`);
1026
+ const allowedRuleKeys = new Set([
1027
+ "conflict",
1028
+ "match",
1029
+ "onProtectionUnavailable",
1030
+ "persistence",
1031
+ "protection",
1032
+ "sensitivity"
1033
+ ]);
1034
+ const unsupportedRuleKey = Object.keys(rule).find((key) => !allowedRuleKeys.has(key));
1035
+ if (unsupportedRuleKey)
1036
+ throw metadataError(id, `localData.mutations[${index}].${unsupportedRuleKey} is not supported.`);
1037
+ const protection = unknownField(rule, "protection");
1038
+ const sensitivity = unknownField(rule, "sensitivity");
1039
+ const persistence = unknownField(rule, "persistence");
1040
+ const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
1041
+ const declaredConflict = unknownField(rule, "conflict");
1042
+ let conflict;
1043
+ if (declaredConflict !== undefined) {
1044
+ const conflictRecord = requireObject(declaredConflict, id, `localData.mutations[${index}].conflict must be an object.`);
1045
+ const unsupportedConflictKey = Object.keys(conflictRecord).find((key) => key !== "maxAttempts" && key !== "strategy");
1046
+ if (unsupportedConflictKey)
1047
+ throw metadataError(id, `localData.mutations[${index}].conflict.${unsupportedConflictKey} is not supported.`);
1048
+ const strategy = unknownField(conflictRecord, "strategy");
1049
+ if (strategy !== "client-wins" && strategy !== "manual" && strategy !== "server-wins")
1050
+ throw metadataError(id, `localData.mutations[${index}].conflict.strategy is invalid.`);
1051
+ const maxAttempts = unknownField(conflictRecord, "maxAttempts");
1052
+ if (maxAttempts !== undefined && strategy !== "client-wins")
1053
+ throw metadataError(id, `localData.mutations[${index}].conflict.maxAttempts requires client-wins.`);
1054
+ conflict = {
1055
+ strategy,
1056
+ ...maxAttempts === undefined ? {} : {
1057
+ maxAttempts: positiveVersion2(maxAttempts, id, `localData.mutations[${index}].conflict.maxAttempts`)
1058
+ }
1059
+ };
1059
1060
  }
1060
- });
1061
- const unmet = [...missing, ...mismatched];
1062
- if (unmet.length > 0)
1063
- throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
1064
- }, directAbsoluteProjectPackages = (projectRoot) => {
1065
- const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
1066
- const packages = new Set;
1067
- for (const field2 of ["dependencies", "devDependencies"]) {
1068
- const dependencies = manifest[field2];
1069
- if (object2(dependencies))
1070
- for (const name of Object.keys(dependencies))
1071
- packages.add(name);
1072
- }
1073
- return packages;
1074
- }, discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
1075
- const root = resolve11(projectRoot);
1076
- if (!existsSync3(root))
1077
- return [];
1078
- const known = new Set(Object.keys(providers));
1079
- const capabilities = new Set;
1080
- for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
1081
- const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
1082
- if (isIgnored(portable))
1083
- continue;
1084
- const source = readFileSync4(resolve11(root, portable), "utf8");
1085
- for (const name of importedCapabilities(source, portable))
1086
- if (known.has(name))
1087
- capabilities.add(name);
1088
- }
1089
- return [...capabilities].sort();
1090
- }, missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
1091
- const packageName = spec.slice(0, spec.lastIndexOf("@"));
1092
- return !directPackages.has(packageName);
1093
- }), projectImportsAbsoluteDeviceCapability = (projectRoot, capability) => {
1094
- const root = resolve11(projectRoot);
1095
- for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
1096
- const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
1097
- if (isIgnored(portable))
1098
- continue;
1099
- const source = readFileSync4(resolve11(root, portable), "utf8");
1100
- if (importedCapabilities(source, portable).has(capability))
1101
- return true;
1102
- }
1103
- return false;
1104
- }, resolveAbsoluteDeviceCapabilityPlan = (projectRoot, provider = "capacitor") => {
1105
- const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot, provider);
1106
- const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
1107
- for (const capability of ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES) {
1108
- if (allProviders[capability] && !capabilities.includes(capability))
1109
- capabilities.push(capability);
1110
- }
1111
- capabilities.sort();
1112
- const providers = {};
1113
- for (const name of capabilities) {
1114
- const capabilityProvider = allProviders[name];
1115
- if (capabilityProvider)
1116
- providers[name] = capabilityProvider;
1117
- }
1061
+ if (protection !== undefined && protection !== "none" && protection !== "required")
1062
+ throw metadataError(id, `localData.mutations[${index}].protection is invalid.`);
1063
+ if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
1064
+ throw metadataError(id, `localData.mutations[${index}].sensitivity is invalid.`);
1065
+ if (onProtectionUnavailable !== undefined && onProtectionUnavailable !== "error" && onProtectionUnavailable !== "memory-only")
1066
+ throw metadataError(id, `localData.mutations[${index}].onProtectionUnavailable is invalid.`);
1067
+ if (persistence !== undefined && persistence !== "durable" && persistence !== "memory-only")
1068
+ throw metadataError(id, `localData.mutations[${index}].persistence is invalid.`);
1069
+ return {
1070
+ match: nonEmpty(Reflect.get(rule, "match"), id, `localData.mutations[${index}].match`),
1071
+ ...conflict ? { conflict } : {},
1072
+ ...sensitivity ? { sensitivity } : {},
1073
+ ...onProtectionUnavailable ? { onProtectionUnavailable } : {},
1074
+ ...persistence ? {
1075
+ persistence
1076
+ } : {},
1077
+ ...protection ? { protection } : {}
1078
+ };
1079
+ }) : undefined;
1080
+ const quota = Reflect.get(record, "maxBytesPerNamespace");
1118
1081
  return {
1119
- capabilities,
1120
- providers,
1121
- requiredPackages: [
1122
- ...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
1123
- ].sort()
1124
- };
1125
- };
1126
- var init_deviceCapabilities = __esm(() => {
1127
- ABSOLUTE_MOBILE_SHELL_DEVICE_CAPABILITIES = [
1128
- "keyboard",
1129
- "systemBars"
1130
- ];
1131
- ADAPTERS = {
1132
- capacitor: "@absolutejs/devices-capacitor",
1133
- expo: "@absolutejs/devices-expo"
1082
+ ...collections ? { collections } : {},
1083
+ ...mutations ? { mutations } : {},
1084
+ ...quota === undefined ? {} : {
1085
+ maxBytesPerNamespace: positiveVersion2(quota, id, "localData.maxBytesPerNamespace")
1086
+ }
1134
1087
  };
1135
- SOURCE_GLOB = new Bun.Glob("**/*.{js,jsx,ts,tsx,svelte,vue}");
1136
- IGNORED_DIRECTORIES = new Set([
1137
- ".absolutejs",
1138
- ".git",
1139
- ".test-builds",
1140
- ".test-shards",
1141
- "build",
1142
- "dist",
1143
- "node_modules",
1144
- "test",
1145
- "tests"
1146
- ]);
1147
- IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
1148
- ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
1149
- IOS_USAGE_DESCRIPTIONS = new Set([
1150
- "camera",
1151
- "location-always",
1152
- "location-when-in-use",
1153
- "photo-library",
1154
- "photo-library-add"
1088
+ }, component = (id, value) => {
1089
+ const record = requireObject(value, id, "localSchema must be an object.");
1090
+ const allowed = new Set([
1091
+ "localData",
1092
+ "migrations",
1093
+ "minimumCompatibleVersion",
1094
+ "version"
1155
1095
  ]);
1156
- IOS_PRIVACY_ACCESSED_API_REASONS = {
1157
- NSPrivacyAccessedAPICategoryFileTimestamp: new Set(["C617.1"])
1096
+ const unsupported = Object.keys(record).find((key) => !allowed.has(key));
1097
+ if (unsupported)
1098
+ throw metadataError(id, `${unsupported} is not supported.`);
1099
+ const version = positiveVersion2(Reflect.get(record, "version"), id, "version");
1100
+ const declaredMinimum = Reflect.get(record, "minimumCompatibleVersion");
1101
+ const minimumCompatibleVersion = declaredMinimum === undefined ? Math.max(1, version - 2) : positiveVersion2(declaredMinimum, id, "minimumCompatibleVersion");
1102
+ const declaredMigrations = Reflect.get(record, "migrations");
1103
+ const declaredLocalData = Reflect.get(record, "localData");
1104
+ if (declaredMigrations !== undefined && !Array.isArray(declaredMigrations))
1105
+ throw metadataError(id, "migrations must be an array.");
1106
+ const migrations = Array.isArray(declaredMigrations) ? declaredMigrations : undefined;
1107
+ return {
1108
+ id,
1109
+ ...declaredLocalData === undefined ? {} : { localData: localDataPolicy(declaredLocalData, id) },
1110
+ minimumCompatibleVersion,
1111
+ ...Array.isArray(migrations) ? {
1112
+ migrations: migrations.map((entry, index) => migration(entry, id, index))
1113
+ } : {},
1114
+ version
1158
1115
  };
1159
- IOS_PRIVACY_ACCESSED_APIS = [
1160
- "NSPrivacyAccessedAPICategoryFileTimestamp"
1116
+ }, dependencyNames = (manifest) => [
1117
+ Reflect.get(manifest, "dependencies"),
1118
+ Reflect.get(manifest, "optionalDependencies"),
1119
+ Reflect.get(manifest, "devDependencies"),
1120
+ Reflect.get(manifest, "peerDependencies")
1121
+ ].flatMap((dependencies) => object2(dependencies) ? Object.keys(dependencies) : []).filter((name, index, names) => names.indexOf(name) === index).sort(), discoverAbsoluteSyncSchema = (projectRoot) => {
1122
+ const appManifestPath = join13(resolve11(projectRoot), "package.json");
1123
+ const appManifest = manifestAt(appManifestPath);
1124
+ if (!appManifest)
1125
+ return {
1126
+ components: [
1127
+ {
1128
+ id: "@absolutejs/app",
1129
+ minimumCompatibleVersion: 1,
1130
+ version: 1
1131
+ }
1132
+ ],
1133
+ sources: []
1134
+ };
1135
+ const appMetadata = localSchemaMetadata(appManifest);
1136
+ const components = [
1137
+ appMetadata === undefined ? { id: "@absolutejs/app", minimumCompatibleVersion: 1, version: 1 } : component("@absolutejs/app", appMetadata)
1138
+ ];
1139
+ const sources = [
1140
+ { id: "@absolutejs/app", manifestPath: appManifestPath }
1161
1141
  ];
1142
+ for (const name of dependencyNames(appManifest)) {
1143
+ const manifestPath = packageManifestPath(projectRoot, name);
1144
+ if (!manifestPath)
1145
+ continue;
1146
+ const manifest = manifestAt(manifestPath);
1147
+ if (!manifest)
1148
+ continue;
1149
+ const metadata = localSchemaMetadata(manifest);
1150
+ if (metadata === undefined)
1151
+ continue;
1152
+ components.push(component(name, metadata));
1153
+ sources.push({ id: name, manifestPath });
1154
+ }
1155
+ components.sort((left, right) => left.id.localeCompare(right.id));
1156
+ sources.sort((left, right) => left.id.localeCompare(right.id));
1157
+ resolveSyncLocalSchemaComponents({}, { components });
1158
+ return { components, sources };
1159
+ };
1160
+ var init_syncSchema = __esm(() => {
1161
+ init_client();
1162
1162
  });
1163
1163
 
1164
1164
  // src/cli/scripts/telemetry.ts
1165
1165
  import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "fs";
1166
1166
  import { homedir as homedir3 } from "os";
1167
- import { join as join17 } from "path";
1167
+ import { join as join18 } from "path";
1168
1168
  var configDir, configPath, getTelemetryConfig = () => {
1169
1169
  try {
1170
1170
  if (!existsSync5(configPath))
@@ -1177,14 +1177,14 @@ var configDir, configPath, getTelemetryConfig = () => {
1177
1177
  }
1178
1178
  };
1179
1179
  var init_telemetry = __esm(() => {
1180
- configDir = join17(homedir3(), ".absolutejs");
1181
- configPath = join17(configDir, "telemetry.json");
1180
+ configDir = join18(homedir3(), ".absolutejs");
1181
+ configPath = join18(configDir, "telemetry.json");
1182
1182
  });
1183
1183
 
1184
1184
  // src/cli/telemetryEvent.ts
1185
1185
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1186
- import { arch, platform as platform2 } from "os";
1187
- import { dirname as dirname12, join as join18, parse } from "path";
1186
+ import { arch, platform as platform3 } from "os";
1187
+ import { dirname as dirname13, join as join19, parse } from "path";
1188
1188
  var checkCandidate = (candidate) => {
1189
1189
  if (!existsSync6(candidate)) {
1190
1190
  return null;
@@ -1204,12 +1204,12 @@ var checkCandidate = (candidate) => {
1204
1204
  }, findPackageVersion = () => {
1205
1205
  let { dir } = import.meta;
1206
1206
  while (dir !== parse(dir).root) {
1207
- const candidate = join18(dir, "package.json");
1207
+ const candidate = join19(dir, "package.json");
1208
1208
  const version = checkCandidate(candidate);
1209
1209
  if (version) {
1210
1210
  return version;
1211
1211
  }
1212
- dir = dirname12(dir);
1212
+ dir = dirname13(dir);
1213
1213
  }
1214
1214
  return "unknown";
1215
1215
  }, sendTelemetryEvent = (event, payload) => {
@@ -1224,7 +1224,7 @@ var checkCandidate = (candidate) => {
1224
1224
  arch: arch(),
1225
1225
  bunVersion: Bun.version,
1226
1226
  event,
1227
- os: platform2(),
1227
+ os: platform3(),
1228
1228
  payload,
1229
1229
  timestamp: new Date().toISOString(),
1230
1230
  version: getVersion()
@@ -8024,28 +8024,194 @@ var readAbsoluteMobileMaterializedReleases = async (root) => {
8024
8024
  }
8025
8025
  };
8026
8026
 
8027
- // src/mobile/buildPipeline.ts
8028
- init_nativeAuth();
8029
- init_syncSchema();
8030
- init_deviceCapabilities();
8031
-
8027
+ // src/mobile/buildPipeline.ts
8028
+ init_deviceCapabilities();
8029
+
8030
+ // src/mobile/expoProject.ts
8031
+ init_nativeAuth();
8032
+ init_syncSchema();
8033
+ init_deviceCapabilities();
8034
+ import {
8035
+ access as access9,
8036
+ cp as cp2,
8037
+ mkdir as mkdir11,
8038
+ mkdtemp as mkdtemp7,
8039
+ readdir as readdir4,
8040
+ readFile as readFile14,
8041
+ rename as rename11,
8042
+ rm as rm10,
8043
+ writeFile as writeFile12
8044
+ } from "fs/promises";
8045
+ import { createHash as createHash11 } from "crypto";
8046
+ import { basename as basename4, dirname as dirname10, join as join14, relative as relative10, resolve as resolve12, sep as sep6 } from "path";
8047
+
8048
+ // src/mobile/updateRuntime.ts
8049
+ init_deviceCapabilities();
8050
+ init_nativeAuth();
8051
+ import { createHash as createHash10 } from "crypto";
8052
+
8053
+ // src/mobile/updateProtocol.ts
8054
+ var ABSOLUTE_MOBILE_UPDATE_FORMAT = 1;
8055
+ var ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES = 32 * 1024 * 1024;
8056
+ var ABSOLUTE_MOBILE_UPDATE_MAX_FILES = 1e4;
8057
+ var ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES = 128 * 1024 * 1024;
8058
+ var ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM = "ecdsa-p256-sha256";
8059
+ var HASH_PATTERN = /^[a-f0-9]{64}$/u;
8060
+ var RELEASE_PATTERN = /^amu_[a-f0-9]{64}$/u;
8061
+ var KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
8062
+ var CHANNEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
8063
+ var isClassification = (value) => value === "bug-fix" || value === "content" || value === "security";
8064
+ var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8065
+ var canonicalValue = (value) => {
8066
+ if (Array.isArray(value))
8067
+ return value.map(canonicalValue);
8068
+ if (!object3(value))
8069
+ return value;
8070
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue(value[key])]));
8071
+ };
8072
+ var absoluteMobileUpdateSigningPayload = (manifest) => new TextEncoder().encode(canonicalizeAbsoluteMobileUpdate(manifest));
8073
+ var canonicalizeAbsoluteMobileUpdate = (value) => JSON.stringify(canonicalValue(value));
8074
+ var requireText2 = (value, field2) => {
8075
+ if (typeof value !== "string" || value.length === 0)
8076
+ throw new TypeError(`${field2} must be a non-empty string.`);
8077
+ return value;
8078
+ };
8079
+ var normalizeAbsoluteMobileUpdatePath = (value) => {
8080
+ const path = requireText2(value, "Update file path").replaceAll("\\", "/");
8081
+ if (path.startsWith("/") || path.includes("\x00") || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
8082
+ throw new TypeError("Update file paths must be normalized relative paths.");
8083
+ }
8084
+ return path;
8085
+ };
8086
+ var parseFile = (value) => {
8087
+ if (!object3(value))
8088
+ throw new TypeError("Update files must be objects.");
8089
+ const path = normalizeAbsoluteMobileUpdatePath(value.path);
8090
+ if (typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes < 0 || value.bytes > ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES) {
8091
+ throw new TypeError(`Update file ${path} has an invalid byte length.`);
8092
+ }
8093
+ if (typeof value.sha256 !== "string" || !HASH_PATTERN.test(value.sha256))
8094
+ throw new TypeError(`Update file ${path} has an invalid SHA-256 digest.`);
8095
+ return { bytes: value.bytes, path, sha256: value.sha256 };
8096
+ };
8097
+ var parseAbsoluteMobileUnsignedUpdateManifest = (value) => {
8098
+ if (!object3(value) || value.format !== ABSOLUTE_MOBILE_UPDATE_FORMAT)
8099
+ throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
8100
+ const appId = requireText2(value.appId, "Update appId");
8101
+ const channel = requireText2(value.channel, "Update channel");
8102
+ if (!CHANNEL_PATTERN.test(channel))
8103
+ throw new TypeError("Update channel contains unsupported characters.");
8104
+ if (!isClassification(value.classification))
8105
+ throw new TypeError("Update classification is invalid.");
8106
+ const createdAt = requireText2(value.createdAt, "Update createdAt");
8107
+ if (!Number.isFinite(Date.parse(createdAt)) || new Date(createdAt).toISOString() !== createdAt)
8108
+ throw new TypeError("Update createdAt must be a canonical ISO timestamp.");
8109
+ if (!Array.isArray(value.files) || value.files.length === 0)
8110
+ throw new TypeError("An update must contain at least one file.");
8111
+ if (value.files.length > ABSOLUTE_MOBILE_UPDATE_MAX_FILES)
8112
+ throw new TypeError("Update contains too many files.");
8113
+ const files = value.files.map(parseFile);
8114
+ const sorted = [...files].sort((left, right) => left.path.localeCompare(right.path));
8115
+ if (files.some((file, index) => file.path !== sorted[index]?.path))
8116
+ throw new TypeError("Update files must be sorted by path.");
8117
+ if (new Set(files.map(({ path }) => path)).size !== files.length)
8118
+ throw new TypeError("Update file paths must be unique.");
8119
+ if (files.reduce((total, file) => total + file.bytes, 0) > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
8120
+ throw new TypeError("Update exceeds the maximum uncompressed size.");
8121
+ if (typeof value.releaseId !== "string" || !RELEASE_PATTERN.test(value.releaseId))
8122
+ throw new TypeError("Update releaseId is invalid.");
8123
+ if (typeof value.runtimeFingerprint !== "string" || !HASH_PATTERN.test(value.runtimeFingerprint))
8124
+ throw new TypeError("Update runtime fingerprint is invalid.");
8125
+ if (value.withinSubmittedPurpose !== true)
8126
+ throw new TypeError("OTA updates must attest that they remain within the submitted app purpose.");
8127
+ return {
8128
+ appId,
8129
+ channel,
8130
+ classification: value.classification,
8131
+ createdAt,
8132
+ files,
8133
+ format: ABSOLUTE_MOBILE_UPDATE_FORMAT,
8134
+ releaseId: value.releaseId,
8135
+ runtimeFingerprint: value.runtimeFingerprint,
8136
+ withinSubmittedPurpose: true
8137
+ };
8138
+ };
8139
+ var parseAbsoluteMobileUpdateManifest = (value) => {
8140
+ if (!object3(value))
8141
+ throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
8142
+ const { signature: signatureValue, ...unsignedValue } = value;
8143
+ const unsigned = parseAbsoluteMobileUnsignedUpdateManifest(unsignedValue);
8144
+ if (!object3(signatureValue))
8145
+ throw new TypeError("Update signature is missing.");
8146
+ if (signatureValue.algorithm !== ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM)
8147
+ throw new TypeError("Update signature algorithm is unsupported.");
8148
+ const keyId = requireText2(signatureValue.keyId, "Update signature keyId");
8149
+ if (!KEY_ID_PATTERN.test(keyId))
8150
+ throw new TypeError("Update signature keyId is invalid.");
8151
+ const signature = requireText2(signatureValue.value, "Update signature value");
8152
+ let signatureBytes;
8153
+ try {
8154
+ signatureBytes = Uint8Array.from(atob(signature), (character) => character.charCodeAt(0));
8155
+ } catch {
8156
+ signatureBytes = new Uint8Array;
8157
+ }
8158
+ if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(signature) || signatureBytes.byteLength !== 64 || btoa(String.fromCharCode(...signatureBytes)) !== signature)
8159
+ throw new TypeError("Update signature is not canonical base64.");
8160
+ return {
8161
+ ...unsigned,
8162
+ signature: {
8163
+ algorithm: ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM,
8164
+ keyId,
8165
+ value: signature
8166
+ }
8167
+ };
8168
+ };
8169
+ var unsignedAbsoluteMobileUpdate = (manifest) => {
8170
+ const { signature: _signature, ...unsigned } = manifest;
8171
+ return unsigned;
8172
+ };
8173
+
8174
+ // src/mobile/updateRuntime.ts
8175
+ init_syncSchema();
8176
+ var ABSOLUTE_MOBILE_SHELL_ABI = 2;
8177
+ var ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT = 1;
8178
+ var createAbsoluteMobileUpdateRuntimeDescriptor = (options) => ({
8179
+ appId: options.config.appId,
8180
+ auth: options.auth ?? null,
8181
+ deepLinks: {
8182
+ hosts: options.config.deepLinkHosts,
8183
+ ...options.config.deepLinkScheme ? { scheme: options.config.deepLinkScheme } : {}
8184
+ },
8185
+ deviceCapabilities: options.deviceCapabilities,
8186
+ engine: options.config.engine,
8187
+ format: ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT,
8188
+ shellAbi: ABSOLUTE_MOBILE_SHELL_ABI,
8189
+ syncSchema: options.syncSchema ?? null,
8190
+ updates: options.config.updates ?? null
8191
+ });
8192
+ var fingerprintAbsoluteMobileUpdateRuntime = (descriptor) => createHash10("sha256").update(canonicalizeAbsoluteMobileUpdate(descriptor)).digest("hex");
8193
+ var resolveAbsoluteMobileUpdateRuntime = (config, projectRoot) => {
8194
+ const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
8195
+ const sync = auth !== undefined && projectUsesAbsoluteSync(projectRoot);
8196
+ const syncSchema = sync ? discoverAbsoluteSyncSchema(projectRoot) : undefined;
8197
+ const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(projectRoot, config.engine);
8198
+ const descriptor = createAbsoluteMobileUpdateRuntimeDescriptor({
8199
+ ...auth ? { auth } : {},
8200
+ config,
8201
+ deviceCapabilities,
8202
+ ...syncSchema ? { syncSchema } : {}
8203
+ });
8204
+ return {
8205
+ auth,
8206
+ descriptor,
8207
+ deviceCapabilities,
8208
+ fingerprint: fingerprintAbsoluteMobileUpdateRuntime(descriptor),
8209
+ sync,
8210
+ syncSchema
8211
+ };
8212
+ };
8213
+
8032
8214
  // src/mobile/expoProject.ts
8033
- init_nativeAuth();
8034
- init_syncSchema();
8035
- init_deviceCapabilities();
8036
- import {
8037
- access as access9,
8038
- cp as cp2,
8039
- mkdir as mkdir11,
8040
- mkdtemp as mkdtemp7,
8041
- readdir as readdir4,
8042
- readFile as readFile14,
8043
- rename as rename11,
8044
- rm as rm10,
8045
- writeFile as writeFile12
8046
- } from "fs/promises";
8047
- import { createHash as createHash10 } from "crypto";
8048
- import { basename as basename4, dirname as dirname10, join as join14, relative as relative10, resolve as resolve12, sep as sep6 } from "path";
8049
8215
  var EXPO_GENERATED_HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
8050
8216
  `;
8051
8217
  var EXPO_ASSET_EXTENSION = ".absasset";
@@ -8122,7 +8288,7 @@ var packageDependencies = (plan) => Object.fromEntries(plan.requiredPackages.map
8122
8288
  const separator = spec.lastIndexOf("@");
8123
8289
  return [spec.slice(0, separator), spec.slice(separator + 1)];
8124
8290
  }));
8125
- var expoPackage = (auth, sync, devices) => ({
8291
+ var expoPackage = (auth, sync, devices, updates) => ({
8126
8292
  dependencies: {
8127
8293
  "@absolutejs/devices": "0.7.0",
8128
8294
  "@absolutejs/devices-expo": "0.0.2",
@@ -8137,6 +8303,7 @@ var expoPackage = (auth, sync, devices) => ({
8137
8303
  expo: "~57.0.9",
8138
8304
  "expo-asset": "~57.0.15",
8139
8305
  "expo-constants": "~57.0.16",
8306
+ ...updates ? { "expo-crypto": "~57.0.1" } : {},
8140
8307
  "expo-dev-client": "~57.0.16",
8141
8308
  "expo-file-system": "~57.0.6",
8142
8309
  "expo-haptics": "~57.0.2",
@@ -8145,13 +8312,13 @@ var expoPackage = (auth, sync, devices) => ({
8145
8312
  "expo-background-task": "~57.0.14",
8146
8313
  "expo-network": "~57.0.1",
8147
8314
  "expo-sqlite": "~57.0.2",
8148
- "expo-task-manager": "~57.0.14",
8149
- "expo-updates": "~57.0.19"
8315
+ "expo-task-manager": "~57.0.14"
8150
8316
  } : {},
8317
+ ...updates || sync ? { "expo-updates": "~57.0.19" } : {},
8151
8318
  "expo-router": "~57.0.17",
8152
- ...auth ? {
8319
+ ...auth || updates ? {
8153
8320
  "expo-secure-store": "~57.0.2",
8154
- "expo-web-browser": "~57.0.2"
8321
+ ...auth ? { "expo-web-browser": "~57.0.2" } : {}
8155
8322
  } : {},
8156
8323
  react: "19.2.3",
8157
8324
  "react-native": "0.86.3",
@@ -8174,7 +8341,7 @@ var expoPackage = (auth, sync, devices) => ({
8174
8341
  },
8175
8342
  version: "0.0.0"
8176
8343
  });
8177
- var expoAppConfig = (config, auth, sync, devices) => {
8344
+ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
8178
8345
  const requirements = absoluteDeviceNativeRequirements(devices);
8179
8346
  const usageKey = (purpose) => {
8180
8347
  if (purpose === "camera")
@@ -8260,7 +8427,7 @@ var expoAppConfig = (config, auth, sync, devices) => {
8260
8427
  plugins: [
8261
8428
  "expo-router",
8262
8429
  ["expo-dev-client", { launchMode: "most-recent" }],
8263
- ...auth ? ["expo-secure-store"] : [],
8430
+ ...auth || config.updates ? ["expo-secure-store"] : [],
8264
8431
  ...sync ? [
8265
8432
  "expo-sqlite",
8266
8433
  "expo-background-task",
@@ -8268,9 +8435,20 @@ var expoAppConfig = (config, auth, sync, devices) => {
8268
8435
  ] : [],
8269
8436
  ...configuredPlugins
8270
8437
  ],
8271
- runtimeVersion: { policy: "appVersion" },
8438
+ runtimeVersion: runtimeFingerprint,
8272
8439
  scheme: config.deepLinkScheme,
8273
8440
  slug: config.appId.toLowerCase().replaceAll(".", "-"),
8441
+ ...config.updates ? {
8442
+ updates: {
8443
+ checkAutomatically: "NEVER",
8444
+ fallbackToCacheTimeout: config.updates.bootTimeoutMs,
8445
+ requestHeaders: {
8446
+ "x-absolute-mobile-app": config.appId,
8447
+ "x-absolute-mobile-channel": config.updates.channel
8448
+ },
8449
+ url: config.updates.manifestUrl
8450
+ }
8451
+ } : {},
8274
8452
  version: config.iosVersion ?? "0.1.0"
8275
8453
  }
8276
8454
  };
@@ -8341,13 +8519,16 @@ config.watchFolders = [appRoot];
8341
8519
 
8342
8520
  module.exports = config;
8343
8521
  `;
8344
- var layoutSource = (auth, sync) => `${EXPO_GENERATED_HEADER}import { Stack } from 'expo-router';
8522
+ var layoutSource = (auth, sync, updates) => `${EXPO_GENERATED_HEADER}import { Stack } from 'expo-router';
8345
8523
  import '../src/generated/AbsoluteDevices';
8346
8524
  ${auth ? `import { useEffect, useState } from 'react';
8347
8525
  import { startAbsoluteExpoAuth } from '../src/generated/AbsoluteAuth';` : ""}
8348
8526
  ${sync ? "import { startAbsoluteExpoSync } from '../src/generated/AbsoluteSync';" : ""}
8527
+ ${updates ? `import { useEffect as useUpdateEffect } from 'react';
8528
+ import { startAbsoluteExpoUpdates } from '../src/generated/AbsoluteUpdates';` : ""}
8349
8529
 
8350
8530
  export default function AbsoluteLayout() {
8531
+ ${updates ? "useUpdateEffect(() => { void startAbsoluteExpoUpdates(); }, []);" : ""}
8351
8532
  ${auth ? `const [ready, setReady] = useState(false);
8352
8533
  useEffect(() => {
8353
8534
  let active = true;
@@ -8358,6 +8539,40 @@ export default function AbsoluteLayout() {
8358
8539
  return <Stack screenOptions={{ headerShown: false }} />;
8359
8540
  }
8360
8541
  `;
8542
+ var updatesRuntimeSource = (config) => `${EXPO_GENERATED_HEADER}import { randomUUID } from 'expo-crypto';
8543
+ import * as SecureStore from 'expo-secure-store';
8544
+ import * as Updates from 'expo-updates';
8545
+
8546
+ const INSTALLATION_KEY = 'absolutejs.mobile.update.installation.v1';
8547
+ const REQUEST_HEADERS = ${JSON.stringify({
8548
+ "x-absolute-mobile-app": config.appId,
8549
+ "x-absolute-mobile-channel": config.updates?.channel
8550
+ })};
8551
+
8552
+ let startPromise: Promise<void> | undefined;
8553
+ export const startAbsoluteExpoUpdates = () => {
8554
+ startPromise ??= (async () => {
8555
+ if (!Updates.isEnabled || __DEV__) return;
8556
+ let installationId = await SecureStore.getItemAsync(INSTALLATION_KEY);
8557
+ if (!installationId) {
8558
+ installationId = randomUUID();
8559
+ await SecureStore.setItemAsync(INSTALLATION_KEY, installationId);
8560
+ }
8561
+ await Updates.setUpdateRequestHeadersOverride({
8562
+ ...REQUEST_HEADERS,
8563
+ 'x-absolute-mobile-installation': installationId
8564
+ });
8565
+ const result = await Updates.checkForUpdateAsync();
8566
+ if (result.isAvailable || result.isRollBackToEmbedded) {
8567
+ await Updates.fetchUpdateAsync();
8568
+ }
8569
+ })().catch(error => {
8570
+ console.warn('[absolute:mobile-update] Expo update check failed', error instanceof Error ? error.message : 'unknown error');
8571
+ });
8572
+
8573
+ return startPromise;
8574
+ };
8575
+ `;
8361
8576
  var nativeDiagnosticSource = `${EXPO_GENERATED_HEADER}import * as Haptics from 'expo-haptics';
8362
8577
  import { Link } from 'expo-router';
8363
8578
  import { Pressable, SafeAreaView, StyleSheet, Text, View } from 'react-native';
@@ -8460,7 +8675,7 @@ ${push ? `absoluteExpoAuth.onPrincipalChange(principal => {
8460
8675
  `;
8461
8676
  };
8462
8677
  var authRuntimeSource = (auth, appId) => {
8463
- const storageIdentity = createHash10("sha256").update(appId).digest("hex").slice(0, 24);
8678
+ const storageIdentity = createHash11("sha256").update(appId).digest("hex").slice(0, 24);
8464
8679
  return `${EXPO_GENERATED_HEADER}import { createAbsoluteExpoAuthClient } from '@absolutejs/auth-expo';
8465
8680
  import { createMobileAuthTransport, installAuthClientRuntimeTransport } from '@absolutejs/auth/client/mobile';
8466
8681
 
@@ -8492,7 +8707,7 @@ export const startAbsoluteExpoAuth = () => {
8492
8707
  `;
8493
8708
  };
8494
8709
  var syncRuntimeSource = (config, schema) => {
8495
- const storageIdentity = createHash10("sha256").update(config.appId).digest("hex").slice(0, 24);
8710
+ const storageIdentity = createHash11("sha256").update(config.appId).digest("hex").slice(0, 24);
8496
8711
  const backgroundEndpoint = new URL("/__absolute/sync/background", config.productionOrigin).href;
8497
8712
  const backgroundTask = `${config.appId}.absolutejs.sync`;
8498
8713
  return `${EXPO_GENERATED_HEADER}import { runHeadlessSync } from '@absolutejs/sync/client/headless';
@@ -9130,6 +9345,7 @@ var writeAbsoluteExpoProject = async (config, options) => {
9130
9345
  const devices = resolveAbsoluteDeviceCapabilityPlan(projectRoot, "expo");
9131
9346
  const syncEnabled = Boolean(auth && projectUsesAbsoluteSync(projectRoot));
9132
9347
  const syncSchema = syncEnabled ? { components: discoverAbsoluteSyncSchema(projectRoot).components } : undefined;
9348
+ const runtimeFingerprint = resolveAbsoluteMobileUpdateRuntime(config, projectRoot).fingerprint;
9133
9349
  const routeModules = Object.entries(config.expoNativeRoutes);
9134
9350
  const moduleChecks = await Promise.all(routeModules.map(async ([route, module]) => ({
9135
9351
  exists: await exists3(module),
@@ -9162,12 +9378,12 @@ node_modules/
9162
9378
  ],
9163
9379
  [
9164
9380
  join14(project, "app.json"),
9165
- jsonSource(expoAppConfig(config, authEnabled, syncEnabled, devices))
9381
+ jsonSource(expoAppConfig(config, authEnabled, syncEnabled, devices, runtimeFingerprint))
9166
9382
  ],
9167
9383
  [join14(project, "app.config.js"), expoDynamicAppConfig],
9168
9384
  [
9169
9385
  join14(project, "package.json"),
9170
- jsonSource(expoPackage(authEnabled, syncEnabled, devices))
9386
+ jsonSource(expoPackage(authEnabled, syncEnabled, devices, config.updates !== undefined))
9171
9387
  ],
9172
9388
  [join14(project, "metro.config.js"), metroConfig(projectRoot)],
9173
9389
  [
@@ -9180,7 +9396,7 @@ node_modules/
9180
9396
  ],
9181
9397
  [
9182
9398
  join14(project, "app", "_layout.tsx"),
9183
- layoutSource(authEnabled, syncEnabled)
9399
+ layoutSource(authEnabled, syncEnabled, config.updates !== undefined)
9184
9400
  ],
9185
9401
  [
9186
9402
  join14(project, "app", "__absolute", "native", "index.tsx"),
@@ -9199,6 +9415,9 @@ node_modules/
9199
9415
  webHostSource(config, auth, syncEnabled)
9200
9416
  ]
9201
9417
  ]);
9418
+ if (config.updates) {
9419
+ files.set(join14(project, "src", "generated", "AbsoluteUpdates.ts"), updatesRuntimeSource(config));
9420
+ }
9202
9421
  if (auth) {
9203
9422
  files.set(join14(project, "src", "generated", "AbsoluteAuth.ts"), authRuntimeSource(auth, config.appId));
9204
9423
  }
@@ -9302,7 +9521,7 @@ var syncAbsoluteExpoWebAssets = async (config) => {
9302
9521
  const nativeDataManifest = expoNativeDataManifest(manifest);
9303
9522
  const { appBuild } = nativeDataManifest;
9304
9523
  const files = await walkFiles(config.bundleDirectory);
9305
- const bundleHash = createHash10("sha256");
9524
+ const bundleHash = createHash11("sha256");
9306
9525
  const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await readFile14(file), file })));
9307
9526
  filesWithContents.forEach(({ contents, file }) => {
9308
9527
  bundleHash.update(relative10(config.bundleDirectory, file).replaceAll("\\", "/"));
@@ -9334,149 +9553,6 @@ var syncAbsoluteExpoWebAssets = async (config) => {
9334
9553
  return { appBuild, assets: assets.length, bundleId, path: destination };
9335
9554
  };
9336
9555
 
9337
- // src/mobile/updateRuntime.ts
9338
- import { createHash as createHash11 } from "crypto";
9339
-
9340
- // src/mobile/updateProtocol.ts
9341
- var ABSOLUTE_MOBILE_UPDATE_FORMAT = 1;
9342
- var ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES = 32 * 1024 * 1024;
9343
- var ABSOLUTE_MOBILE_UPDATE_MAX_FILES = 1e4;
9344
- var ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES = 128 * 1024 * 1024;
9345
- var ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM = "ecdsa-p256-sha256";
9346
- var HASH_PATTERN = /^[a-f0-9]{64}$/u;
9347
- var RELEASE_PATTERN = /^amu_[a-f0-9]{64}$/u;
9348
- var KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
9349
- var CHANNEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
9350
- var isClassification = (value) => value === "bug-fix" || value === "content" || value === "security";
9351
- var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9352
- var canonicalValue = (value) => {
9353
- if (Array.isArray(value))
9354
- return value.map(canonicalValue);
9355
- if (!object3(value))
9356
- return value;
9357
- return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue(value[key])]));
9358
- };
9359
- var absoluteMobileUpdateSigningPayload = (manifest) => new TextEncoder().encode(canonicalizeAbsoluteMobileUpdate(manifest));
9360
- var canonicalizeAbsoluteMobileUpdate = (value) => JSON.stringify(canonicalValue(value));
9361
- var requireText2 = (value, field2) => {
9362
- if (typeof value !== "string" || value.length === 0)
9363
- throw new TypeError(`${field2} must be a non-empty string.`);
9364
- return value;
9365
- };
9366
- var normalizeAbsoluteMobileUpdatePath = (value) => {
9367
- const path = requireText2(value, "Update file path").replaceAll("\\", "/");
9368
- if (path.startsWith("/") || path.includes("\x00") || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
9369
- throw new TypeError("Update file paths must be normalized relative paths.");
9370
- }
9371
- return path;
9372
- };
9373
- var parseFile = (value) => {
9374
- if (!object3(value))
9375
- throw new TypeError("Update files must be objects.");
9376
- const path = normalizeAbsoluteMobileUpdatePath(value.path);
9377
- if (typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes < 0 || value.bytes > ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES) {
9378
- throw new TypeError(`Update file ${path} has an invalid byte length.`);
9379
- }
9380
- if (typeof value.sha256 !== "string" || !HASH_PATTERN.test(value.sha256))
9381
- throw new TypeError(`Update file ${path} has an invalid SHA-256 digest.`);
9382
- return { bytes: value.bytes, path, sha256: value.sha256 };
9383
- };
9384
- var parseAbsoluteMobileUnsignedUpdateManifest = (value) => {
9385
- if (!object3(value) || value.format !== ABSOLUTE_MOBILE_UPDATE_FORMAT)
9386
- throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
9387
- const appId = requireText2(value.appId, "Update appId");
9388
- const channel = requireText2(value.channel, "Update channel");
9389
- if (!CHANNEL_PATTERN.test(channel))
9390
- throw new TypeError("Update channel contains unsupported characters.");
9391
- if (!isClassification(value.classification))
9392
- throw new TypeError("Update classification is invalid.");
9393
- const createdAt = requireText2(value.createdAt, "Update createdAt");
9394
- if (!Number.isFinite(Date.parse(createdAt)) || new Date(createdAt).toISOString() !== createdAt)
9395
- throw new TypeError("Update createdAt must be a canonical ISO timestamp.");
9396
- if (!Array.isArray(value.files) || value.files.length === 0)
9397
- throw new TypeError("An update must contain at least one file.");
9398
- if (value.files.length > ABSOLUTE_MOBILE_UPDATE_MAX_FILES)
9399
- throw new TypeError("Update contains too many files.");
9400
- const files = value.files.map(parseFile);
9401
- const sorted = [...files].sort((left, right) => left.path.localeCompare(right.path));
9402
- if (files.some((file, index) => file.path !== sorted[index]?.path))
9403
- throw new TypeError("Update files must be sorted by path.");
9404
- if (new Set(files.map(({ path }) => path)).size !== files.length)
9405
- throw new TypeError("Update file paths must be unique.");
9406
- if (files.reduce((total, file) => total + file.bytes, 0) > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
9407
- throw new TypeError("Update exceeds the maximum uncompressed size.");
9408
- if (typeof value.releaseId !== "string" || !RELEASE_PATTERN.test(value.releaseId))
9409
- throw new TypeError("Update releaseId is invalid.");
9410
- if (typeof value.runtimeFingerprint !== "string" || !HASH_PATTERN.test(value.runtimeFingerprint))
9411
- throw new TypeError("Update runtime fingerprint is invalid.");
9412
- if (value.withinSubmittedPurpose !== true)
9413
- throw new TypeError("OTA updates must attest that they remain within the submitted app purpose.");
9414
- return {
9415
- appId,
9416
- channel,
9417
- classification: value.classification,
9418
- createdAt,
9419
- files,
9420
- format: ABSOLUTE_MOBILE_UPDATE_FORMAT,
9421
- releaseId: value.releaseId,
9422
- runtimeFingerprint: value.runtimeFingerprint,
9423
- withinSubmittedPurpose: true
9424
- };
9425
- };
9426
- var parseAbsoluteMobileUpdateManifest = (value) => {
9427
- if (!object3(value))
9428
- throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
9429
- const { signature: signatureValue, ...unsignedValue } = value;
9430
- const unsigned = parseAbsoluteMobileUnsignedUpdateManifest(unsignedValue);
9431
- if (!object3(signatureValue))
9432
- throw new TypeError("Update signature is missing.");
9433
- if (signatureValue.algorithm !== ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM)
9434
- throw new TypeError("Update signature algorithm is unsupported.");
9435
- const keyId = requireText2(signatureValue.keyId, "Update signature keyId");
9436
- if (!KEY_ID_PATTERN.test(keyId))
9437
- throw new TypeError("Update signature keyId is invalid.");
9438
- const signature = requireText2(signatureValue.value, "Update signature value");
9439
- let signatureBytes;
9440
- try {
9441
- signatureBytes = Uint8Array.from(atob(signature), (character) => character.charCodeAt(0));
9442
- } catch {
9443
- signatureBytes = new Uint8Array;
9444
- }
9445
- if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(signature) || signatureBytes.byteLength !== 64 || btoa(String.fromCharCode(...signatureBytes)) !== signature)
9446
- throw new TypeError("Update signature is not canonical base64.");
9447
- return {
9448
- ...unsigned,
9449
- signature: {
9450
- algorithm: ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM,
9451
- keyId,
9452
- value: signature
9453
- }
9454
- };
9455
- };
9456
- var unsignedAbsoluteMobileUpdate = (manifest) => {
9457
- const { signature: _signature, ...unsigned } = manifest;
9458
- return unsigned;
9459
- };
9460
-
9461
- // src/mobile/updateRuntime.ts
9462
- var ABSOLUTE_MOBILE_SHELL_ABI = 2;
9463
- var ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT = 1;
9464
- var createAbsoluteMobileUpdateRuntimeDescriptor = (options) => ({
9465
- appId: options.config.appId,
9466
- auth: options.auth ?? null,
9467
- deepLinks: {
9468
- hosts: options.config.deepLinkHosts,
9469
- ...options.config.deepLinkScheme ? { scheme: options.config.deepLinkScheme } : {}
9470
- },
9471
- deviceCapabilities: options.deviceCapabilities,
9472
- engine: options.config.engine,
9473
- format: ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT,
9474
- shellAbi: ABSOLUTE_MOBILE_SHELL_ABI,
9475
- syncSchema: options.syncSchema ?? null,
9476
- updates: options.config.updates ?? null
9477
- });
9478
- var fingerprintAbsoluteMobileUpdateRuntime = (descriptor) => createHash11("sha256").update(canonicalizeAbsoluteMobileUpdate(descriptor)).digest("hex");
9479
-
9480
9556
  // src/mobile/buildPipeline.ts
9481
9557
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
9482
9558
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
@@ -9548,16 +9624,13 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
9548
9624
  producerPath: resolve13(options.producerPath),
9549
9625
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
9550
9626
  });
9551
- const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
9552
- const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
9553
- const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
9554
- const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot, mobile.engine);
9555
- const runtimeFingerprint = fingerprintAbsoluteMobileUpdateRuntime(createAbsoluteMobileUpdateRuntimeDescriptor({
9556
- ...auth ? { auth } : {},
9557
- config: mobile,
9627
+ const {
9628
+ auth,
9558
9629
  deviceCapabilities,
9559
- ...syncSchema ? { syncSchema } : {}
9560
- }));
9630
+ fingerprint: runtimeFingerprint,
9631
+ sync,
9632
+ syncSchema
9633
+ } = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot);
9561
9634
  const usesPush = deviceCapabilities.capabilities.includes("pushNotifications");
9562
9635
  if (usesPush && !auth)
9563
9636
  throw new TypeError("Portable push notifications require @absolutejs/auth so provider tokens can be registered without exposing identity controls to page code.");
@@ -10284,14 +10357,102 @@ var startAbsoluteExpoDevSession = async (options) => {
10284
10357
  throw error;
10285
10358
  }
10286
10359
  };
10360
+ // src/mobile/expoUpdate.ts
10361
+ import { access as access11, mkdir as mkdir12, readFile as readFile16, writeFile as writeFile13 } from "fs/promises";
10362
+ import { dirname as dirname11, extname as extname4, join as join17 } from "path";
10363
+ var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
10364
+ var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
10365
+ var object4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10366
+ var safePath = (value, field2) => {
10367
+ if (typeof value !== "string" || value.length === 0)
10368
+ throw new TypeError(`Expo update ${field2} is invalid.`);
10369
+ const path = value.replaceAll("\\", "/");
10370
+ if (path.startsWith("/") || path.split("/").some((segment) => !segment || segment === "." || segment === ".."))
10371
+ throw new TypeError(`Expo update ${field2} must be a relative path.`);
10372
+ return path;
10373
+ };
10374
+ var extension = (value, path) => {
10375
+ if (value === null || value === undefined) {
10376
+ const inferred = extname4(path);
10377
+ return inferred ? inferred.slice(1) : undefined;
10378
+ }
10379
+ if (typeof value !== "string" || !/^[A-Za-z0-9]+$/u.test(value))
10380
+ throw new TypeError("Expo update asset extension is invalid.");
10381
+ return value;
10382
+ };
10383
+ var asset = (value) => {
10384
+ if (!object4(value))
10385
+ throw new TypeError("Expo update asset is invalid.");
10386
+ const path = safePath(value.path, "asset path");
10387
+ const normalizedExtension = extension(value.extension ?? value.ext, path);
10388
+ return {
10389
+ ...normalizedExtension ? { extension: normalizedExtension } : {},
10390
+ path
10391
+ };
10392
+ };
10393
+ var platform2 = (value) => {
10394
+ if (!object4(value) || !Array.isArray(value.assets))
10395
+ throw new TypeError("Expo update platform metadata is invalid.");
10396
+ const launchValue = object4(value.launchAsset) ? value.launchAsset : { path: value.bundle };
10397
+ const launchPath = safePath(launchValue.path, "launch bundle");
10398
+ const launchExtension = extension(launchValue.extension ?? null, launchPath);
10399
+ const assets = value.assets.map(asset);
10400
+ return {
10401
+ assets,
10402
+ launchAsset: {
10403
+ ...launchExtension ? { extension: launchExtension } : {},
10404
+ path: launchPath
10405
+ }
10406
+ };
10407
+ };
10408
+ var parseAbsoluteExpoUpdateDescriptor = (value) => {
10409
+ if (!object4(value) || value.engine !== "expo" || value.format !== ABSOLUTE_EXPO_UPDATE_FORMAT || !object4(value.expoConfig) || !object4(value.platforms) || typeof value.runtimeVersion !== "string" || !/^[a-f0-9]{64}$/u.test(value.runtimeVersion))
10410
+ throw new TypeError("AbsoluteJS Expo update descriptor is invalid.");
10411
+ const platforms = {};
10412
+ for (const name of ["android", "ios"]) {
10413
+ const candidate = value.platforms[name];
10414
+ if (candidate !== undefined)
10415
+ platforms[name] = platform2(candidate);
10416
+ }
10417
+ if (Object.keys(platforms).length === 0)
10418
+ throw new TypeError("Expo update descriptor has no native platforms.");
10419
+ return {
10420
+ engine: "expo",
10421
+ expoConfig: value.expoConfig,
10422
+ format: ABSOLUTE_EXPO_UPDATE_FORMAT,
10423
+ platforms,
10424
+ runtimeVersion: value.runtimeVersion
10425
+ };
10426
+ };
10427
+ var finalizeAbsoluteExpoUpdateExport = async (options) => {
10428
+ const metadata = JSON.parse(await readFile16(join17(options.exportDirectory, "metadata.json"), "utf8"));
10429
+ if (!object4(metadata) || !object4(metadata.fileMetadata))
10430
+ throw new TypeError("Expo export metadata.json is invalid.");
10431
+ const descriptor = parseAbsoluteExpoUpdateDescriptor({
10432
+ engine: "expo",
10433
+ expoConfig: options.expoConfig,
10434
+ format: ABSOLUTE_EXPO_UPDATE_FORMAT,
10435
+ platforms: metadata.fileMetadata,
10436
+ runtimeVersion: options.runtimeVersion
10437
+ });
10438
+ const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
10439
+ await Promise.all(referenced.map(({ path }) => access11(join17(options.exportDirectory, path)).catch(() => {
10440
+ throw new TypeError(`Expo export metadata references missing asset ${path}.`);
10441
+ })));
10442
+ const destination = join17(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
10443
+ await mkdir12(dirname11(destination), { recursive: true });
10444
+ await writeFile13(destination, `${JSON.stringify(descriptor, null, "\t")}
10445
+ `);
10446
+ return { descriptor, path: destination };
10447
+ };
10287
10448
 
10288
10449
  // src/mobile/index.ts
10289
10450
  init_config();
10290
10451
 
10291
10452
  // src/mobile/ciWorkflow.ts
10292
10453
  import { existsSync as existsSync4 } from "fs";
10293
- import { access as access11, mkdir as mkdir12, readFile as readFile16, writeFile as writeFile13 } from "fs/promises";
10294
- import { dirname as dirname11, extname as extname4, relative as relative11, resolve as resolve14, sep as sep7 } from "path";
10454
+ import { access as access12, mkdir as mkdir13, readFile as readFile17, writeFile as writeFile14 } from "fs/promises";
10455
+ import { dirname as dirname12, extname as extname5, relative as relative11, resolve as resolve14, sep as sep7 } from "path";
10295
10456
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
10296
10457
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
10297
10458
  var CI_ENV_INDENTATION = 6;
@@ -10312,7 +10473,7 @@ var RESERVED_SECRET_NAMES = new Set([
10312
10473
  ]);
10313
10474
  var exists4 = async (path) => {
10314
10475
  try {
10315
- await access11(path);
10476
+ await access12(path);
10316
10477
  return true;
10317
10478
  } catch {
10318
10479
  return false;
@@ -10337,7 +10498,7 @@ var workflowOutputPath = (projectRoot, value) => {
10337
10498
  const workflows = resolve14(root, ".github/workflows");
10338
10499
  const path = resolve14(root, value ?? ".github/workflows/absolute-mobile.yml");
10339
10500
  const portable = relative11(workflows, path);
10340
- if (portable === ".." || portable.startsWith(`..${sep7}`) || extname4(path) !== ".yml" && extname4(path) !== ".yaml") {
10501
+ if (portable === ".." || portable.startsWith(`..${sep7}`) || extname5(path) !== ".yml" && extname5(path) !== ".yaml") {
10341
10502
  throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
10342
10503
  }
10343
10504
  return path;
@@ -10394,13 +10555,13 @@ var bundleAuditSteps = ` - name: Prepare production mobile bundle
10394
10555
  - name: Enforce mobile bundle audit
10395
10556
  if: steps.mobile-bundle-audit.outcome != 'success'
10396
10557
  run: exit 1`;
10397
- var releaseAuditSteps = (platform2) => ` - name: Run redacted mobile release audit
10558
+ var releaseAuditSteps = (platform3) => ` - name: Run redacted mobile release audit
10398
10559
  id: mobile-release-audit
10399
10560
  continue-on-error: true
10400
10561
  shell: bash
10401
10562
  run: |
10402
10563
  mkdir -p .absolutejs/mobile-ci
10403
- args=(bunx absolute mobile doctor release ${platform2} --json)
10564
+ args=(bunx absolute mobile doctor release ${platform3} --json)
10404
10565
  ${appendConfigArgument}
10405
10566
  "\${args[@]}" > .absolutejs/mobile-ci/compliance.json
10406
10567
  - name: Upload mobile compliance report
@@ -10467,7 +10628,7 @@ var publishingInputs = (platforms, includePublishing) => {
10467
10628
  ${fields.join(`
10468
10629
  `)}`;
10469
10630
  };
10470
- var jobCondition = (platform2) => `github.event_name == 'workflow_dispatch' && (inputs.platform == 'all' || inputs.platform == '${platform2}')`;
10631
+ var jobCondition = (platform3) => `github.event_name == 'workflow_dispatch' && (inputs.platform == 'all' || inputs.platform == '${platform3}')`;
10471
10632
  var androidJob = (options) => {
10472
10633
  const custom = customSecretEnvironment(options.customSecrets);
10473
10634
  const publishEnvironment = options.includePublishing ? `
@@ -10766,18 +10927,18 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
10766
10927
  var writeAbsoluteMobileGithubWorkflow = async (options) => {
10767
10928
  const path = workflowOutputPath(options.projectRoot, options.outputPath);
10768
10929
  const generated = createAbsoluteMobileGithubWorkflow(options);
10769
- const previous = await exists4(path) ? await readFile16(path, "utf8") : undefined;
10930
+ const previous = await exists4(path) ? await readFile17(path, "utf8") : undefined;
10770
10931
  if (previous !== undefined && previous !== generated.workflow && !options.force)
10771
10932
  throw new TypeError(`${relative11(options.projectRoot, path)} already exists and differs. Rerun with --force to replace the generated workflow.`);
10772
10933
  if (previous !== generated.workflow) {
10773
- await mkdir12(dirname11(path), { recursive: true });
10774
- await writeFile13(path, generated.workflow);
10934
+ await mkdir13(dirname12(path), { recursive: true });
10935
+ await writeFile14(path, generated.workflow);
10775
10936
  }
10776
10937
  return {
10777
10938
  changed: previous !== generated.workflow,
10778
10939
  format: ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
10779
10940
  path,
10780
- platforms: options.config.engine === "expo" ? options.config.platforms.filter((platform2) => platform2 === "android") : [...options.config.platforms].sort(),
10941
+ platforms: options.config.engine === "expo" ? options.config.platforms.filter((platform3) => platform3 === "android") : [...options.config.platforms].sort(),
10781
10942
  publishing: options.includePublishing === true,
10782
10943
  requiredSecrets: generated.requiredSecrets
10783
10944
  };
@@ -10984,13 +11145,13 @@ var createAbsoluteMobilePreviewPlugin = (mobile) => {
10984
11145
  })).post("/__absolute/mobile-preview-telemetry", ({ body, status }) => {
10985
11146
  const value = isRecord10(body) ? body : undefined;
10986
11147
  const durationMs = value?.durationMs;
10987
- const platform3 = value?.platform;
10988
- if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || durationMs > 300000 || platform3 !== "android" && platform3 !== "ios") {
11148
+ const platform4 = value?.platform;
11149
+ if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || durationMs > 300000 || platform4 !== "android" && platform4 !== "ios") {
10989
11150
  return status(400, { error: "invalid-preview-telemetry" });
10990
11151
  }
10991
11152
  sendTelemetryEvent("mobile:preview-ready", {
10992
11153
  durationMs: Math.round(durationMs),
10993
- platform: platform3,
11154
+ platform: platform4,
10994
11155
  provider: "capacitor",
10995
11156
  target: "mobile-preview"
10996
11157
  });
@@ -10998,19 +11159,19 @@ var createAbsoluteMobilePreviewPlugin = (mobile) => {
10998
11159
  });
10999
11160
  };
11000
11161
  // src/mobile/nativeDeepLinks.ts
11001
- import { readFile as readFile17, rename as rename12, writeFile as writeFile14 } from "fs/promises";
11002
- import { join as join19 } from "path";
11162
+ import { readFile as readFile18, rename as rename12, writeFile as writeFile15 } from "fs/promises";
11163
+ import { join as join20 } from "path";
11003
11164
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
11004
11165
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
11005
11166
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
11006
11167
  var NOT_FOUND = -1;
11007
11168
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
11008
11169
  var writeChangedFile = async (path, source) => {
11009
- const current = await readFile17(path, "utf8");
11170
+ const current = await readFile18(path, "utf8");
11010
11171
  if (current === source)
11011
11172
  return false;
11012
11173
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
11013
- await writeFile14(temporary, source, { flag: "wx" });
11174
+ await writeFile15(temporary, source, { flag: "wx" });
11014
11175
  await rename12(temporary, path);
11015
11176
  return true;
11016
11177
  };
@@ -11056,8 +11217,8 @@ ${hosts}
11056
11217
  `;
11057
11218
  };
11058
11219
  var configureAndroid = async (config) => {
11059
- const path = join19(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
11060
- const source = await readFile17(path, "utf8");
11220
+ const path = join20(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
11221
+ const source = await readFile18(path, "utf8");
11061
11222
  const mainActivity = source.indexOf('android:name=".MainActivity"');
11062
11223
  if (mainActivity === NOT_FOUND) {
11063
11224
  throw new TypeError("Android MainActivity was not found.");
@@ -11082,8 +11243,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
11082
11243
  ${END_MARKER}
11083
11244
  `;
11084
11245
  var configureIosInfo = async (config) => {
11085
- const path = join19(config.nativeProjectDirectory, "ios/App/App/Info.plist");
11086
- const source = await readFile17(path, "utf8");
11246
+ const path = join20(config.nativeProjectDirectory, "ios/App/App/Info.plist");
11247
+ const source = await readFile18(path, "utf8");
11087
11248
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
11088
11249
  ${END_MARKER}
11089
11250
  `;
@@ -11106,10 +11267,10 @@ ${domains}
11106
11267
  `;
11107
11268
  };
11108
11269
  var configureIosEntitlements = async (config) => {
11109
- const path = join19(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
11270
+ const path = join20(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
11110
11271
  let current = "";
11111
11272
  try {
11112
- current = await readFile17(path, "utf8");
11273
+ current = await readFile18(path, "utf8");
11113
11274
  } catch (error) {
11114
11275
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
11115
11276
  throw error;
@@ -11119,13 +11280,13 @@ var configureIosEntitlements = async (config) => {
11119
11280
  if (current === source)
11120
11281
  return false;
11121
11282
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
11122
- await writeFile14(temporary, source, { flag: "wx" });
11283
+ await writeFile15(temporary, source, { flag: "wx" });
11123
11284
  await rename12(temporary, path);
11124
11285
  return true;
11125
11286
  };
11126
11287
  var configureIosProject = async (config) => {
11127
- const path = join19(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
11128
- const source = await readFile17(path, "utf8");
11288
+ const path = join20(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
11289
+ const source = await readFile18(path, "utf8");
11129
11290
  const declarations = [
11130
11291
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
11131
11292
  ].map((match) => match[1]);
@@ -11154,18 +11315,18 @@ var configureIos = async (config) => {
11154
11315
  return changed.some(Boolean);
11155
11316
  };
11156
11317
  var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms) => {
11157
- const results = await Promise.all(platforms.map(async (platform3) => {
11158
- const didChange = platform3 === "android" ? await configureAndroid(config) : await configureIos(config);
11159
- return { didChange, platform: platform3 };
11318
+ const results = await Promise.all(platforms.map(async (platform4) => {
11319
+ const didChange = platform4 === "android" ? await configureAndroid(config) : await configureIos(config);
11320
+ return { didChange, platform: platform4 };
11160
11321
  }));
11161
11322
  return {
11162
- changed: results.filter(({ didChange }) => didChange).map(({ platform: platform3 }) => platform3)
11323
+ changed: results.filter(({ didChange }) => didChange).map(({ platform: platform4 }) => platform4)
11163
11324
  };
11164
11325
  };
11165
11326
  // src/mobile/nativeDeviceCapabilities.ts
11166
11327
  init_deviceCapabilities();
11167
- import { readFile as readFile18, rename as rename13, writeFile as writeFile15 } from "fs/promises";
11168
- import { join as join20 } from "path";
11328
+ import { readFile as readFile19, rename as rename13, writeFile as writeFile16 } from "fs/promises";
11329
+ import { join as join21 } from "path";
11169
11330
  var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
11170
11331
  var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
11171
11332
  var NOT_FOUND2 = -1;
@@ -11175,11 +11336,11 @@ var PUSH_START_MARKER = "absolutejs:push-notifications:start";
11175
11336
  var PUSH_END_MARKER = "absolutejs:push-notifications:end";
11176
11337
  var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
11177
11338
  var writeChangedFile2 = async (path, source) => {
11178
- const current = await readFile18(path, "utf8");
11339
+ const current = await readFile19(path, "utf8");
11179
11340
  if (current === source)
11180
11341
  return false;
11181
11342
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
11182
- await writeFile15(temporary, source, { flag: "wx" });
11343
+ await writeFile16(temporary, source, { flag: "wx" });
11183
11344
  await rename13(temporary, path);
11184
11345
  return true;
11185
11346
  };
@@ -11188,14 +11349,14 @@ var writeOptionalChangedFile = async (path, source) => {
11188
11349
  if (current === source)
11189
11350
  return false;
11190
11351
  if (current === null) {
11191
- await writeFile15(path, source, { flag: "wx" });
11352
+ await writeFile16(path, source, { flag: "wx" });
11192
11353
  return true;
11193
11354
  }
11194
11355
  return writeChangedFile2(path, source);
11195
11356
  };
11196
11357
  var optionalSource = async (path) => {
11197
11358
  try {
11198
- return await readFile18(path, "utf8");
11359
+ return await readFile19(path, "utf8");
11199
11360
  } catch (error) {
11200
11361
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
11201
11362
  return null;
@@ -11309,14 +11470,14 @@ var writeIosPrivacyManifest = async (path, current, source) => {
11309
11470
  return false;
11310
11471
  if (current !== null)
11311
11472
  return writeChangedFile2(path, source);
11312
- await writeFile15(path, source, { flag: "wx" });
11473
+ await writeFile16(path, source, { flag: "wx" });
11313
11474
  return true;
11314
11475
  };
11315
11476
  var configureIosPrivacyProject = async (config, requirements) => {
11316
11477
  if (requirements.iosPrivacyAccessedApis.length === 0)
11317
11478
  return false;
11318
- const projectPath2 = join20(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
11319
- const project = await readFile18(projectPath2, "utf8");
11479
+ const projectPath2 = join21(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
11480
+ const project = await readFile19(projectPath2, "utf8");
11320
11481
  return writeChangedFile2(projectPath2, addIosPrivacyProjectReference(project));
11321
11482
  };
11322
11483
  var addIosPrivacyProjectReference = (source) => {
@@ -11368,8 +11529,8 @@ ${next.slice(index)}`;
11368
11529
  return next;
11369
11530
  };
11370
11531
  var configureIos2 = async (config, plan) => {
11371
- const path = join20(config.nativeProjectDirectory, "ios/App/App/Info.plist");
11372
- const source = await readFile18(path, "utf8");
11532
+ const path = join21(config.nativeProjectDirectory, "ios/App/App/Info.plist");
11533
+ const source = await readFile19(path, "utf8");
11373
11534
  const requirements = absoluteDeviceNativeRequirements(plan);
11374
11535
  const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
11375
11536
  const ownedStart = source.indexOf(START_MARKER2);
@@ -11389,7 +11550,7 @@ ${content}
11389
11550
  ${END_MARKER2}
11390
11551
  ` : "";
11391
11552
  const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
11392
- const privacyPath = join20(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
11553
+ const privacyPath = join21(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
11393
11554
  const privacyCurrent = await optionalSource(privacyPath);
11394
11555
  const privacySource = privacyManifestSource(privacyCurrent, requirements);
11395
11556
  const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
@@ -11400,7 +11561,7 @@ ${content}
11400
11561
  return infoChanged || privacyChanged || projectChanged || pushChanged;
11401
11562
  };
11402
11563
  var configureIosPushNotifications = async (config, enabled) => {
11403
- const entitlementsPath = join20(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
11564
+ const entitlementsPath = join21(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
11404
11565
  const entitlements = await optionalSource(entitlementsPath);
11405
11566
  if (entitlements === null && !enabled)
11406
11567
  return false;
@@ -11412,7 +11573,7 @@ var configureIosPushNotifications = async (config, enabled) => {
11412
11573
  <!-- ${PUSH_END_MARKER} -->
11413
11574
  ` : "";
11414
11575
  const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
11415
- const delegatePath = join20(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
11576
+ const delegatePath = join21(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
11416
11577
  const delegate = await optionalSource(delegatePath);
11417
11578
  if (delegate === null && !enabled)
11418
11579
  return false;
@@ -11461,8 +11622,8 @@ var replacePushRegion = (source, region, insertion) => {
11461
11622
  return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
11462
11623
  };
11463
11624
  var configureAndroid2 = async (config, plan) => {
11464
- const path = join20(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
11465
- const source = await readFile18(path, "utf8");
11625
+ const path = join21(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
11626
+ const source = await readFile19(path, "utf8");
11466
11627
  const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
11467
11628
  const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
11468
11629
  `);
@@ -11497,17 +11658,17 @@ ${content}
11497
11658
  throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
11498
11659
  const [manifestChanged, firebaseChanged] = await Promise.all([
11499
11660
  writeChangedFile2(path, nextManifest),
11500
- writeOptionalChangedFile(join20(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
11661
+ writeOptionalChangedFile(join21(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
11501
11662
  ]);
11502
11663
  return manifestChanged || firebaseChanged;
11503
11664
  };
11504
11665
  var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
11505
- const results = await Promise.all(platforms.map(async (platform3) => ({
11506
- didChange: platform3 === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
11507
- platform: platform3
11666
+ const results = await Promise.all(platforms.map(async (platform4) => ({
11667
+ didChange: platform4 === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
11668
+ platform: platform4
11508
11669
  })));
11509
11670
  return {
11510
- changed: results.filter(({ didChange }) => didChange).map(({ platform: platform3 }) => platform3)
11671
+ changed: results.filter(({ didChange }) => didChange).map(({ platform: platform4 }) => platform4)
11511
11672
  };
11512
11673
  };
11513
11674
 
@@ -11516,14 +11677,14 @@ init_nativeAuth();
11516
11677
 
11517
11678
  // src/mobile/nativeUpdates.ts
11518
11679
  import {
11519
- mkdir as mkdir13,
11520
- readFile as readFile19,
11680
+ mkdir as mkdir14,
11681
+ readFile as readFile20,
11521
11682
  readdir as readdir5,
11522
11683
  rename as rename14,
11523
11684
  rm as rm11,
11524
- writeFile as writeFile16
11685
+ writeFile as writeFile17
11525
11686
  } from "fs/promises";
11526
- import { dirname as dirname13, join as join21 } from "path";
11687
+ import { dirname as dirname14, join as join22 } from "path";
11527
11688
  var START = "// absolutejs:mobile-updates:start";
11528
11689
  var END = "// absolutejs:mobile-updates:end";
11529
11690
  var PLUGIN_START = "// absolutejs:mobile-update-plugin:start";
@@ -11531,7 +11692,7 @@ var PLUGIN_END = "// absolutejs:mobile-update-plugin:end";
11531
11692
  var ANDROID_PLUGIN = "AbsoluteMobileUpdateWatchdogPlugin.java";
11532
11693
  var optionalSource2 = async (path) => {
11533
11694
  try {
11534
- return await readFile19(path, "utf8");
11695
+ return await readFile20(path, "utf8");
11535
11696
  } catch (error) {
11536
11697
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
11537
11698
  return null;
@@ -11543,13 +11704,13 @@ var writeChanged = async (path, source) => {
11543
11704
  const current = await optionalSource2(path);
11544
11705
  if (current === source)
11545
11706
  return false;
11546
- await mkdir13(dirname13(path), { recursive: true });
11707
+ await mkdir14(dirname14(path), { recursive: true });
11547
11708
  if (current === null) {
11548
- await writeFile16(path, source, { flag: "wx" });
11709
+ await writeFile17(path, source, { flag: "wx" });
11549
11710
  return true;
11550
11711
  }
11551
11712
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
11552
- await writeFile16(temporary, source, { flag: "wx" });
11713
+ await writeFile17(temporary, source, { flag: "wx" });
11553
11714
  await rename14(temporary, path);
11554
11715
  return true;
11555
11716
  };
@@ -11690,9 +11851,9 @@ public final class AbsoluteMobileUpdateWatchdogPlugin: CAPPlugin, CAPBridgedPlug
11690
11851
  ${PLUGIN_END}
11691
11852
  `;
11692
11853
  var configureIos3 = async (config, enabled) => {
11693
- const path = join21(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
11694
- const capacitorConfigPath = join21(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
11695
- let source = await readFile19(path, "utf8");
11854
+ const path = join22(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
11855
+ const capacitorConfigPath = join22(config.nativeProjectDirectory, "ios/App/App/capacitor.config.json");
11856
+ let source = await readFile20(path, "utf8");
11696
11857
  const launch = source.indexOf("didFinishLaunchingWithOptions");
11697
11858
  const brace = launch < 0 ? -1 : source.indexOf("{", launch);
11698
11859
  const launchRegion = enabled ? ` ${START}
@@ -11703,7 +11864,7 @@ var configureIos3 = async (config, enabled) => {
11703
11864
  `, brace) + 1, "Could not find a safe iOS location for mobile update recovery.");
11704
11865
  const plugin = enabled ? iosPluginRegion(config.updates?.bootTimeoutMs ?? 20000) : "";
11705
11866
  source = replaceMarkedRegion(source, PLUGIN_START, PLUGIN_END, plugin, source.length, "Could not find a safe iOS location for the mobile update watchdog.");
11706
- const capacitorConfigSource2 = await readFile19(capacitorConfigPath, "utf8");
11867
+ const capacitorConfigSource2 = await readFile20(capacitorConfigPath, "utf8");
11707
11868
  let capacitorConfig;
11708
11869
  try {
11709
11870
  const value = JSON.parse(capacitorConfigSource2);
@@ -11881,12 +12042,12 @@ var androidActivityInRoot = async (root) => {
11881
12042
  throw error;
11882
12043
  }
11883
12044
  const candidate = entries.find((entry) => entry.endsWith("/MainActivity.java") || entry.endsWith("/MainActivity.kt") || entry === "MainActivity.java" || entry === "MainActivity.kt");
11884
- return candidate ? join21(root, candidate) : undefined;
12045
+ return candidate ? join22(root, candidate) : undefined;
11885
12046
  };
11886
12047
  var androidActivity = async (config) => {
11887
12048
  const roots = [
11888
- join21(config.nativeProjectDirectory, "android/app/src/main/java"),
11889
- join21(config.nativeProjectDirectory, "android/app/src/main/kotlin")
12049
+ join22(config.nativeProjectDirectory, "android/app/src/main/java"),
12050
+ join22(config.nativeProjectDirectory, "android/app/src/main/kotlin")
11890
12051
  ];
11891
12052
  const candidates = await Promise.all(roots.map(androidActivityInRoot));
11892
12053
  const candidate = candidates.find((value) => value !== undefined);
@@ -11985,12 +12146,12 @@ var injectAndroidActivity = (source, enabled) => {
11985
12146
  };
11986
12147
  var configureAndroid3 = async (config, enabled) => {
11987
12148
  const activityPath = await androidActivity(config);
11988
- const activity = await readFile19(activityPath, "utf8");
12149
+ const activity = await readFile20(activityPath, "utf8");
11989
12150
  const packageName = activity.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*[;\n]/mu)?.[1];
11990
12151
  if (!packageName)
11991
12152
  throw new TypeError("Android MainActivity package declaration was not found.");
11992
12153
  const packagePath = packageName.replaceAll(".", "/");
11993
- const pluginPath = join21(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
12154
+ const pluginPath = join22(config.nativeProjectDirectory, "android/app/src/main/java", packagePath, ANDROID_PLUGIN);
11994
12155
  const changed = await Promise.all([
11995
12156
  writeChanged(activityPath, injectAndroidActivity(activity, enabled)),
11996
12157
  enabled ? writeChanged(pluginPath, androidPluginSource(packageName, config.updates?.bootTimeoutMs ?? 20000)) : removeOwnedFile(pluginPath)
@@ -12009,7 +12170,7 @@ var applyAbsoluteNativeUpdates = async (config, platforms = config.platforms) =>
12009
12170
  return { changed: changed.some(Boolean) };
12010
12171
  };
12011
12172
  // src/mobile/releasePublisher.ts
12012
- import { access as access12 } from "fs/promises";
12173
+ import { access as access13 } from "fs/promises";
12013
12174
  import { isAbsolute as isAbsolute6, relative as relative12, resolve as resolve15, sep as sep8 } from "path";
12014
12175
  import { pathToFileURL as pathToFileURL3 } from "url";
12015
12176
  var prepareAbsoluteIosRelease = async (publisher, options) => {
@@ -12046,7 +12207,7 @@ var publisherModulePath = (projectRoot, requested) => {
12046
12207
  };
12047
12208
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
12048
12209
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
12049
- await access12(modulePath).catch(() => {
12210
+ await access13(modulePath).catch(() => {
12050
12211
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
12051
12212
  });
12052
12213
  const loaded = await import(pathToFileURL3(modulePath).href);
@@ -12108,7 +12269,7 @@ var publishAbsoluteIosRelease = async (options) => {
12108
12269
  };
12109
12270
  // src/mobile/routeMetadataTransform.ts
12110
12271
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
12111
- import { dirname as dirname14, extname as extname5, relative as relative13, resolve as resolve16 } from "path";
12272
+ import { dirname as dirname15, extname as extname6, relative as relative13, resolve as resolve16 } from "path";
12112
12273
  import ts2 from "typescript";
12113
12274
  var ROUTE_METHODS = new Set(["get", "head"]);
12114
12275
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
@@ -12159,7 +12320,7 @@ var PAGE_HANDLERS = new Map([
12159
12320
  ]
12160
12321
  ]);
12161
12322
  var posixPath = (value) => value.replace(/\\/g, "/");
12162
- var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname14(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
12323
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname15(entry), existsSync7, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync7, "tsconfig.json");
12163
12324
  var createProgram = (entry, projectRoot) => {
12164
12325
  const configPath2 = findTsconfig(entry, projectRoot);
12165
12326
  if (!configPath2) {
@@ -12171,7 +12332,7 @@ var createProgram = (entry, projectRoot) => {
12171
12332
  target: ts2.ScriptTarget.ESNext
12172
12333
  });
12173
12334
  }
12174
- const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync7(path, "utf8")).config, ts2.sys, dirname14(configPath2));
12335
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath2, (path) => readFileSync7(path, "utf8")).config, ts2.sys, dirname15(configPath2));
12175
12336
  if (!parsed.fileNames.includes(entry))
12176
12337
  parsed.fileNames.push(entry);
12177
12338
  return ts2.createProgram(parsed.fileNames, parsed.options);
@@ -12185,8 +12346,8 @@ var propertyName = (property) => {
12185
12346
  return property.name.text;
12186
12347
  return;
12187
12348
  };
12188
- var objectPropertyExpression = (object4, name) => {
12189
- const property = object4.properties.find((candidate) => propertyName(candidate) === name);
12349
+ var objectPropertyExpression = (object5, name) => {
12350
+ const property = object5.properties.find((candidate) => propertyName(candidate) === name);
12190
12351
  if (property && ts2.isPropertyAssignment(property)) {
12191
12352
  return property.initializer;
12192
12353
  }
@@ -12382,8 +12543,8 @@ var spreadObject = (expression, checker, bindings) => {
12382
12543
  return;
12383
12544
  return callableObject(expression, checker);
12384
12545
  };
12385
- var objectAssetKey = (object4, name, checker, bindings = new Map) => {
12386
- for (const property of [...object4.properties].reverse()) {
12546
+ var objectAssetKey = (object5, name, checker, bindings = new Map) => {
12547
+ for (const property of [...object5.properties].reverse()) {
12387
12548
  if (propertyName(property) === name && ts2.isShorthandPropertyAssignment(property)) {
12388
12549
  return assetKeyWithBindings(property.name, checker, bindings);
12389
12550
  }
@@ -12622,7 +12783,7 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
12622
12783
  const source = await Bun.file(path).text();
12623
12784
  return {
12624
12785
  contents: transformFile(source, path, analysis),
12625
- loader: extname5(path).endsWith("x") ? "tsx" : "ts"
12786
+ loader: extname6(path).endsWith("x") ? "tsx" : "ts"
12626
12787
  };
12627
12788
  });
12628
12789
  }
@@ -13170,15 +13331,15 @@ var createAbsoluteMobileUpdateClient = (options) => {
13170
13331
  const file = manifest.files[index];
13171
13332
  if (!file)
13172
13333
  return received;
13173
- const asset = await request(fileUrl(manifestUrl, manifest.releaseId, file.path), {
13334
+ const asset2 = await request(fileUrl(manifestUrl, manifest.releaseId, file.path), {
13174
13335
  cache: "no-store",
13175
13336
  credentials: "omit",
13176
13337
  redirect: "error",
13177
13338
  signal: AbortSignal.timeout(30000)
13178
13339
  });
13179
- if (!asset.ok)
13180
- throw new TypeError(`Mobile update asset ${file.path} failed with HTTP ${asset.status}.`);
13181
- const contents = await readBounded(asset, file.bytes);
13340
+ if (!asset2.ok)
13341
+ throw new TypeError(`Mobile update asset ${file.path} failed with HTTP ${asset2.status}.`);
13342
+ const contents = await readBounded(asset2, file.bytes);
13182
13343
  const total = received + contents.byteLength;
13183
13344
  if (contents.byteLength !== file.bytes || total > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
13184
13345
  throw new TypeError(`Mobile update asset ${file.path} has an invalid size.`);
@@ -13233,7 +13394,7 @@ var createAbsoluteMobileUpdateClient = (options) => {
13233
13394
  };
13234
13395
  };
13235
13396
  // src/mobile/updatePublisher.ts
13236
- import { access as access13 } from "fs/promises";
13397
+ import { access as access14 } from "fs/promises";
13237
13398
  import { isAbsolute as isAbsolute7, relative as relative15, resolve as resolve18, sep as sep9 } from "path";
13238
13399
  import { pathToFileURL as pathToFileURL4 } from "url";
13239
13400
 
@@ -13241,23 +13402,23 @@ import { pathToFileURL as pathToFileURL4 } from "url";
13241
13402
  import { createHash as createHash12, sign, verify } from "crypto";
13242
13403
  import {
13243
13404
  cp as cp3,
13244
- mkdir as mkdir14,
13405
+ mkdir as mkdir15,
13245
13406
  mkdtemp as mkdtemp8,
13246
13407
  readdir as readdir6,
13247
- readFile as readFile20,
13408
+ readFile as readFile21,
13248
13409
  rename as rename15,
13249
13410
  rm as rm12,
13250
13411
  stat as stat4,
13251
- writeFile as writeFile17
13412
+ writeFile as writeFile18
13252
13413
  } from "fs/promises";
13253
- import { dirname as dirname15, join as join22, relative as relative14, resolve as resolve17 } from "path";
13414
+ import { dirname as dirname16, join as join23, relative as relative14, resolve as resolve17 } from "path";
13254
13415
  var UPDATE_MANIFEST_FILE = "update.json";
13255
13416
  var UPDATE_FILES_DIRECTORY = "files";
13256
13417
  var sha2562 = (value) => createHash12("sha256").update(value).digest("hex");
13257
13418
  var listFiles = async (root, directory = root) => {
13258
13419
  const entries = await readdir6(directory, { withFileTypes: true });
13259
13420
  const paths = await Promise.all(entries.map(async (entry) => {
13260
- const path = join22(directory, entry.name);
13421
+ const path = join23(directory, entry.name);
13261
13422
  if (entry.isDirectory())
13262
13423
  return listFiles(root, path);
13263
13424
  if (!entry.isFile())
@@ -13267,10 +13428,10 @@ var listFiles = async (root, directory = root) => {
13267
13428
  return paths.flat().sort((left, right) => left.localeCompare(right));
13268
13429
  };
13269
13430
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
13270
- const source = join22(root, path);
13431
+ const source = join23(root, path);
13271
13432
  const [metadata, contents] = await Promise.all([
13272
13433
  stat4(source),
13273
- readFile20(source)
13434
+ readFile21(source)
13274
13435
  ]);
13275
13436
  return { bytes: metadata.size, path, sha256: sha2562(contents) };
13276
13437
  }));
@@ -13305,15 +13466,15 @@ var buildAbsoluteMobileUpdate = async (options) => {
13305
13466
  value: signature.toString("base64")
13306
13467
  }
13307
13468
  });
13308
- await mkdir14(outputRoot, { recursive: true });
13309
- const outputDirectory = join22(outputRoot, manifest.releaseId);
13310
- const staging = await mkdtemp8(join22(outputRoot, ".stage-"));
13469
+ await mkdir15(outputRoot, { recursive: true });
13470
+ const outputDirectory = join23(outputRoot, manifest.releaseId);
13471
+ const staging = await mkdtemp8(join23(outputRoot, ".stage-"));
13311
13472
  try {
13312
- await cp3(bundleDirectory, join22(staging, UPDATE_FILES_DIRECTORY), {
13473
+ await cp3(bundleDirectory, join23(staging, UPDATE_FILES_DIRECTORY), {
13313
13474
  force: true,
13314
13475
  recursive: true
13315
13476
  });
13316
- await writeFile17(join22(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
13477
+ await writeFile18(join23(staging, UPDATE_MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
13317
13478
  `);
13318
13479
  await rename15(staging, outputDirectory);
13319
13480
  } catch (error) {
@@ -13324,7 +13485,7 @@ var buildAbsoluteMobileUpdate = async (options) => {
13324
13485
  }
13325
13486
  return {
13326
13487
  manifest,
13327
- manifestPath: join22(outputDirectory, UPDATE_MANIFEST_FILE),
13488
+ manifestPath: join23(outputDirectory, UPDATE_MANIFEST_FILE),
13328
13489
  outputDirectory
13329
13490
  };
13330
13491
  };
@@ -13333,10 +13494,10 @@ var copyAbsoluteMobileUpdateFile = async (updateDirectory, path, destination) =>
13333
13494
  const root = resolve17(updateDirectory, UPDATE_FILES_DIRECTORY);
13334
13495
  if (!source.startsWith(`${root}/`))
13335
13496
  throw new TypeError("Update file escaped its release.");
13336
- await mkdir14(dirname15(destination), { recursive: true });
13497
+ await mkdir15(dirname16(destination), { recursive: true });
13337
13498
  await cp3(source, destination, { force: true });
13338
13499
  };
13339
- var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile20(join22(resolve17(directory), UPDATE_MANIFEST_FILE), "utf8")));
13500
+ var readAbsoluteMobileUpdate = async (directory) => parseAbsoluteMobileUpdateManifest(JSON.parse(await readFile21(join23(resolve17(directory), UPDATE_MANIFEST_FILE), "utf8")));
13340
13501
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
13341
13502
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
13342
13503
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -13346,8 +13507,8 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
13346
13507
  };
13347
13508
 
13348
13509
  // src/mobile/updatePublisher.ts
13349
- var object4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13350
- var isPublisher2 = (value) => object4(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
13510
+ var object5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13511
+ var isPublisher2 = (value) => object5(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
13351
13512
  var projectPath2 = (projectRoot, requested, label) => {
13352
13513
  const root = resolve18(projectRoot);
13353
13514
  const path = resolve18(root, requested);
@@ -13358,11 +13519,11 @@ var projectPath2 = (projectRoot, requested, label) => {
13358
13519
  };
13359
13520
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
13360
13521
  const modulePath = projectPath2(projectRoot, requestedModulePath, "mobile update registry");
13361
- await access13(modulePath).catch(() => {
13522
+ await access14(modulePath).catch(() => {
13362
13523
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
13363
13524
  });
13364
13525
  const loaded = await import(pathToFileURL4(modulePath).href);
13365
- const publisher = object4(loaded) ? loaded.default ?? loaded.registry : undefined;
13526
+ const publisher = object5(loaded) ? loaded.default ?? loaded.registry : undefined;
13366
13527
  if (!isPublisher2(publisher))
13367
13528
  throw new TypeError("Mobile update registry must implement publishUpdate, promoteUpdate, and rollbackUpdate.");
13368
13529
  return publisher;
@@ -13449,6 +13610,8 @@ export {
13449
13610
  ABSOLUTE_EXPO_SYNC_CORE_VERSION,
13450
13611
  ABSOLUTE_EXPO_SYNC_PACKAGE,
13451
13612
  ABSOLUTE_EXPO_SYNC_VERSION,
13613
+ ABSOLUTE_EXPO_UPDATE_DESCRIPTOR,
13614
+ ABSOLUTE_EXPO_UPDATE_FORMAT,
13452
13615
  ABSOLUTE_IOS_RELEASE_FORMAT,
13453
13616
  ABSOLUTE_IOS_SIMULATOR_NAME,
13454
13617
  ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
@@ -13533,6 +13696,7 @@ export {
13533
13696
  discoverAbsoluteSyncSchema,
13534
13697
  disposeAbsoluteMobilePage,
13535
13698
  fetchAbsoluteMobilePage,
13699
+ finalizeAbsoluteExpoUpdateExport,
13536
13700
  finalizeAbsoluteMobileCompatibilityBuild,
13537
13701
  finalizeAbsoluteMobilePage,
13538
13702
  fingerprintAbsoluteIosDevProject,
@@ -13576,6 +13740,7 @@ export {
13576
13740
  pairAbsoluteRemoteMac,
13577
13741
  parseAbsoluteAndroidInstalledApp,
13578
13742
  parseAbsoluteExpoBridgeMessage,
13743
+ parseAbsoluteExpoUpdateDescriptor,
13579
13744
  parseAbsoluteIosHmrLog,
13580
13745
  parseAbsoluteIosLogLine,
13581
13746
  parseAbsoluteMobileBuildPageMetadata,
@@ -13614,6 +13779,7 @@ export {
13614
13779
  resolveAbsoluteMobileDeepLink,
13615
13780
  resolveAbsoluteMobileNavigation,
13616
13781
  resolveAbsoluteMobileRoute,
13782
+ resolveAbsoluteMobileUpdateRuntime,
13617
13783
  retainAbsoluteMobileCompatibilityArtifacts,
13618
13784
  rollbackAbsoluteMobileUpdate,
13619
13785
  runAbsoluteAndroidUpgradeConformance,
@@ -13641,5 +13807,5 @@ export {
13641
13807
  writeAbsoluteMobileGithubWorkflow
13642
13808
  };
13643
13809
 
13644
- //# debugId=D63B9AFC1696EC0C64756E2164756E21
13810
+ //# debugId=CBACAEA8DBA2A4F164756E2164756E21
13645
13811
  //# sourceMappingURL=index.js.map