@absolutejs/absolute 0.20.0-beta.3 → 0.20.0-beta.31

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.
Files changed (79) hide show
  1. package/README.md +167 -0
  2. package/dist/angular/browser.js +15 -1
  3. package/dist/angular/browser.js.map +3 -3
  4. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  5. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  6. package/dist/angular/index.js +341 -22
  7. package/dist/angular/index.js.map +8 -5
  8. package/dist/angular/server.js +341 -22
  9. package/dist/angular/server.js.map +8 -5
  10. package/dist/build.js +2058 -1105
  11. package/dist/build.js.map +17 -13
  12. package/dist/cli/index.js +4085 -1139
  13. package/dist/dev/client/cssUtils.ts +16 -2
  14. package/dist/dev/client/handlers/rebuild.ts +11 -1
  15. package/dist/dev/client/hmrClient.ts +32 -3
  16. package/dist/dev/client/hmrTiming.ts +14 -7
  17. package/dist/dev/client/syncDevtools.ts +237 -0
  18. package/dist/index.js +2483 -1367
  19. package/dist/index.js.map +27 -22
  20. package/dist/mobile/browser.js +123 -1
  21. package/dist/mobile/browser.js.map +6 -4
  22. package/dist/mobile/index.js +2948 -328
  23. package/dist/mobile/index.js.map +27 -13
  24. package/dist/mobile/remoteMacAgentEntry.js +29 -0
  25. package/dist/mobile/shellAuth.js +36 -0
  26. package/dist/mobile/shellBootstrap.js +806 -0
  27. package/dist/mobile/shellSync.js +173 -0
  28. package/dist/src/angular/pageHandler.d.ts +3 -0
  29. package/dist/src/build/pwa.d.ts +16 -0
  30. package/dist/src/cli/config/server.d.ts +1 -1
  31. package/dist/src/core/devBuild.d.ts +1 -0
  32. package/dist/src/core/pageHandlers.d.ts +11 -2
  33. package/dist/src/core/prepare.d.ts +24 -0
  34. package/dist/src/mobile/androidEmulatorController.d.ts +6 -1
  35. package/dist/src/mobile/androidTestReport.d.ts +14 -0
  36. package/dist/src/mobile/androidUpgradeConformance.d.ts +47 -0
  37. package/dist/src/mobile/androidWebView.d.ts +1 -0
  38. package/dist/src/mobile/browser.d.ts +1 -0
  39. package/dist/src/mobile/buildPipeline.d.ts +1 -0
  40. package/dist/src/mobile/capacitorBundle.d.ts +22 -1
  41. package/dist/src/mobile/client.d.ts +4 -0
  42. package/dist/src/mobile/config.d.ts +1 -0
  43. package/dist/src/mobile/devDeviceAdapter.d.ts +3 -0
  44. package/dist/src/mobile/deviceCapabilities.d.ts +46 -0
  45. package/dist/src/mobile/index.d.ts +8 -0
  46. package/dist/src/mobile/iosTestReport.d.ts +23 -0
  47. package/dist/src/mobile/nativeAuth.d.ts +17 -0
  48. package/dist/src/mobile/nativeBackgroundSync.d.ts +4 -0
  49. package/dist/src/mobile/nativeDeviceCapabilities.d.ts +6 -0
  50. package/dist/src/mobile/nativeTestReport.d.ts +85 -0
  51. package/dist/src/mobile/releaseArtifact.d.ts +2 -0
  52. package/dist/src/mobile/remoteMacAgent.d.ts +2 -0
  53. package/dist/src/mobile/remoteMacAgentEntry.d.ts +1 -0
  54. package/dist/src/mobile/remoteMacProtocol.d.ts +114 -0
  55. package/dist/src/mobile/remoteMacWire.d.ts +2 -0
  56. package/dist/src/mobile/shellAuth.d.ts +15 -0
  57. package/dist/src/mobile/shellBootstrap.d.ts +22 -1
  58. package/dist/src/mobile/shellHttp.d.ts +2 -0
  59. package/dist/src/mobile/shellPush.d.ts +11 -0
  60. package/dist/src/mobile/shellSync.d.ts +47 -0
  61. package/dist/src/mobile/staticDocument.d.ts +5 -0
  62. package/dist/src/mobile/syncRemediation.d.ts +10 -0
  63. package/dist/src/mobile/syncSchema.d.ts +9 -0
  64. package/dist/src/mobile/transport.d.ts +16 -1
  65. package/dist/src/plugins/imageOptimizer.d.ts +1 -1
  66. package/dist/src/svelte/pageHandler.d.ts +3 -0
  67. package/dist/src/utils/imageProcessing.d.ts +3 -0
  68. package/dist/src/utils/loadConfig.d.ts +1 -0
  69. package/dist/src/vue/pageHandler.d.ts +3 -0
  70. package/dist/svelte/index.js +312 -23
  71. package/dist/svelte/index.js.map +7 -4
  72. package/dist/svelte/server.js +307 -18
  73. package/dist/svelte/server.js.map +7 -4
  74. package/dist/types/build.d.ts +29 -0
  75. package/dist/vue/index.js +312 -23
  76. package/dist/vue/index.js.map +7 -4
  77. package/dist/vue/server.js +307 -18
  78. package/dist/vue/server.js.map +7 -4
  79. package/package.json +38 -10
@@ -159,6 +159,702 @@ var init_startupBanner = __esm(() => {
159
159
  ];
160
160
  });
161
161
 
162
+ // src/utils/stringModifiers.ts
163
+ var normalizeSlug = (str) => str.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9\-_]+/g, "").replace(/[-_]{2,}/g, "-"), toKebab = (str) => normalizeSlug(str).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), toPascal = (str) => {
164
+ if (!str.includes("-") && !str.includes("_")) {
165
+ return str.charAt(0).toUpperCase() + str.slice(1);
166
+ }
167
+ return normalizeSlug(str).split(/[-_]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()).join("");
168
+ };
169
+
170
+ // node_modules/@absolutejs/sync/dist/client/index.js
171
+ var RUNTIME_TRANSPORT, host, isRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients")), registry, SyncLocalDataPolicyError, SyncLocalStoreSchemaError, positiveVersion = (value, label) => {
172
+ if (!Number.isSafeInteger(value) || value < 1)
173
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", `${label} must be a positive safe integer`);
174
+ return value;
175
+ }, isSchemaBundle = (schema) => ("components" in schema), validatePolicyMatch = (match, label) => {
176
+ if (match.length === 0 || match.trim() !== match || /^\*+$/.test(match) || match.includes("**"))
177
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.match must be an exact name or a non-empty glob without adjacent wildcards.`);
178
+ }, validateSyncLocalDataPolicy = (policy, label = "localData") => {
179
+ if (policy.maxBytesPerNamespace !== undefined && (!Number.isSafeInteger(policy.maxBytesPerNamespace) || policy.maxBytesPerNamespace < 1))
180
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.maxBytesPerNamespace must be a positive safe integer.`);
181
+ for (const [index, rule] of (policy.collections ?? []).entries()) {
182
+ validatePolicyMatch(rule.match, `${label}.collections[${index}]`);
183
+ if (rule.maxAgeMs !== undefined && (!Number.isSafeInteger(rule.maxAgeMs) || rule.maxAgeMs < 1))
184
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}].maxAgeMs must be a positive safe integer.`);
185
+ if (rule.persistence === "memory-only" && rule.protection === "required")
186
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}] cannot require at-rest protection when it is memory-only.`);
187
+ if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
188
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.collections[${index}] declares ${rule.sensitivity} data without required protection or memory-only persistence.`);
189
+ }
190
+ for (const [index, rule] of (policy.mutations ?? []).entries()) {
191
+ validatePolicyMatch(rule.match, `${label}.mutations[${index}]`);
192
+ if (rule.conflict !== undefined && rule.conflict.strategy !== "client-wins" && rule.conflict.strategy !== "manual" && rule.conflict.strategy !== "server-wins")
193
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.strategy is invalid.`);
194
+ if (rule.conflict?.maxAttempts !== undefined && (!Number.isSafeInteger(rule.conflict.maxAttempts) || rule.conflict.maxAttempts < 1))
195
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts must be a positive safe integer.`);
196
+ if (rule.conflict?.maxAttempts !== undefined && rule.conflict.strategy !== "client-wins")
197
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}].conflict.maxAttempts is only valid for client-wins.`);
198
+ if (rule.persistence === "memory-only" && rule.protection === "required")
199
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] cannot require at-rest protection when it is memory-only.`);
200
+ if (rule.sensitivity !== undefined && rule.sensitivity !== "public" && rule.protection !== "required" && rule.persistence !== "memory-only")
201
+ throw new SyncLocalDataPolicyError("INVALID_POLICY", `${label}.mutations[${index}] declares ${rule.sensitivity} arguments without required protection.`);
202
+ }
203
+ return policy;
204
+ }, normalizeSyncLocalSchemaComponents = (schema = { version: 1 }) => {
205
+ const components = isSchemaBundle(schema) ? [...schema.components] : [{ ...schema, id: "@absolutejs/app" }];
206
+ const ids = new Set;
207
+ for (const component of components) {
208
+ if (typeof component.id !== "string" || component.id.trim() !== component.id || component.id.length === 0)
209
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", "Sync schema component id must be non-empty and trimmed");
210
+ if (ids.has(component.id))
211
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", `Sync schema component "${component.id}" is declared more than once`);
212
+ ids.add(component.id);
213
+ if (component.localData)
214
+ validateSyncLocalDataPolicy(component.localData, `${component.id}.localData`);
215
+ }
216
+ return components.sort((a, b) => a.id.localeCompare(b.id));
217
+ }, resolveSyncLocalSchemaComponents = (storedVersions, schema = { version: 1 }) => {
218
+ const components = normalizeSyncLocalSchemaComponents(schema).map((component) => {
219
+ const current = resolveSyncLocalMigrations(component.version, component);
220
+ return {
221
+ id: component.id,
222
+ ...resolveSyncLocalMigrations(storedVersions[component.id] ?? current.minimumCompatibleVersion, component)
223
+ };
224
+ });
225
+ const active = new Set(components.map((component) => component.id));
226
+ const orphanedComponents = Object.keys(storedVersions).filter((id) => !active.has(id)).sort();
227
+ return { components, orphanedComponents };
228
+ }, resolveSyncLocalMigrations = (storedVersion, schema = { version: 1 }) => {
229
+ positiveVersion(storedVersion, "Stored Sync schema version");
230
+ const targetVersion = positiveVersion(schema.version, "Target Sync schema version");
231
+ const migrations = [...schema.migrations ?? []].sort((a, b) => a.toVersion - b.toVersion);
232
+ const versions = new Set;
233
+ for (const migration of migrations) {
234
+ positiveVersion(migration.toVersion, "Sync migration toVersion");
235
+ if (versions.has(migration.toVersion))
236
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", `Sync migration ${migration.toVersion} is declared more than once`);
237
+ versions.add(migration.toVersion);
238
+ }
239
+ const inferredMinimum = migrations[0] ? migrations[0].toVersion - 1 : targetVersion;
240
+ const minimumCompatibleVersion = positiveVersion(schema.minimumCompatibleVersion ?? inferredMinimum, "Minimum compatible Sync schema version");
241
+ if (minimumCompatibleVersion > targetVersion)
242
+ throw new SyncLocalStoreSchemaError("INVALID_PLAN", "Minimum compatible Sync schema version cannot exceed its target");
243
+ if (storedVersion > targetVersion)
244
+ throw new SyncLocalStoreSchemaError("SCHEMA_TOO_NEW", `Stored Sync schema ${storedVersion} is newer than this runtime's schema ${targetVersion}`, { storedVersion, targetVersion });
245
+ if (storedVersion < minimumCompatibleVersion)
246
+ throw new SyncLocalStoreSchemaError("SCHEMA_TOO_OLD", `Stored Sync schema ${storedVersion} is older than the minimum compatible schema ${minimumCompatibleVersion}`, { storedVersion, targetVersion });
247
+ const steps = [];
248
+ for (let version = storedVersion + 1;version <= targetVersion; version++) {
249
+ const migration = migrations.find((candidate) => candidate.toVersion === version);
250
+ if (migration === undefined)
251
+ throw new SyncLocalStoreSchemaError("MIGRATION_MISSING", `Sync migration ${version - 1} -> ${version} is missing`, { storedVersion, targetVersion });
252
+ steps.push(migration);
253
+ }
254
+ return { minimumCompatibleVersion, steps, targetVersion };
255
+ };
256
+ var init_client = __esm(() => {
257
+ RUNTIME_TRANSPORT = Symbol.for("@absolutejs/sync/client-runtime-transport");
258
+ host = globalThis;
259
+ registry = (() => {
260
+ const existing = host[RUNTIME_TRANSPORT];
261
+ if (isRegistry(existing))
262
+ return existing;
263
+ if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
264
+ Reflect.set(existing, "clients", []);
265
+ return existing;
266
+ }
267
+ const created = { clients: [], installations: [] };
268
+ Object.defineProperty(host, RUNTIME_TRANSPORT, {
269
+ configurable: false,
270
+ enumerable: false,
271
+ value: created,
272
+ writable: false
273
+ });
274
+ return created;
275
+ })();
276
+ SyncLocalDataPolicyError = class SyncLocalDataPolicyError extends Error {
277
+ code;
278
+ constructor(code, message) {
279
+ super(message);
280
+ this.name = "SyncLocalDataPolicyError";
281
+ this.code = code;
282
+ }
283
+ };
284
+ SyncLocalStoreSchemaError = class SyncLocalStoreSchemaError extends Error {
285
+ code;
286
+ storedVersion;
287
+ targetVersion;
288
+ constructor(code, message, versions = {}) {
289
+ super(message);
290
+ this.name = "SyncLocalStoreSchemaError";
291
+ this.code = code;
292
+ this.storedVersion = versions.storedVersion;
293
+ this.targetVersion = versions.targetVersion;
294
+ }
295
+ };
296
+ });
297
+
298
+ // src/mobile/syncSchema.ts
299
+ import { readFileSync as readFileSync3 } from "fs";
300
+ import { dirname as dirname9, join as join12, resolve as resolve10 } from "path";
301
+ var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value), manifestAt = (path) => {
302
+ try {
303
+ const value = JSON.parse(readFileSync3(path, "utf8"));
304
+ return object(value) ? value : undefined;
305
+ } catch {
306
+ return;
307
+ }
308
+ }, localSchemaMetadata = (manifest) => {
309
+ const absolutejs = Reflect.get(manifest, "absolutejs");
310
+ if (!object(absolutejs))
311
+ return;
312
+ const sync = Reflect.get(absolutejs, "sync");
313
+ if (!object(sync))
314
+ return;
315
+ return Reflect.get(sync, "localSchema");
316
+ }, packageManifestPath = (projectRoot, packageName) => {
317
+ let directory = resolve10(projectRoot);
318
+ while (true) {
319
+ const candidate = join12(directory, "node_modules", packageName, "package.json");
320
+ const manifest = manifestAt(candidate);
321
+ if (manifest && Reflect.get(manifest, "name") === packageName)
322
+ return candidate;
323
+ const parent = dirname9(directory);
324
+ if (parent === directory)
325
+ return;
326
+ directory = parent;
327
+ }
328
+ }, metadataError = (id, detail) => new TypeError(`Invalid AbsoluteJS Sync schema metadata for ${id}: ${detail}`), positiveVersion2 = (value, id, field2) => {
329
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
330
+ throw metadataError(id, `${field2} must be a positive safe integer.`);
331
+ return value;
332
+ }, nonEmpty = (value, id, field2) => {
333
+ if (typeof value !== "string" || value.trim() !== value || value.length === 0)
334
+ throw metadataError(id, `${field2} must be a non-empty trimmed string.`);
335
+ return value;
336
+ }, requireObject = (value, id, detail) => {
337
+ if (!object(value))
338
+ throw metadataError(id, detail);
339
+ return value;
340
+ }, unknownField = (record, key) => record[key], normalizeJsonValue2 = (value, id, field2) => {
341
+ if (value === null || typeof value === "string" || typeof value === "boolean")
342
+ return value;
343
+ if (typeof value === "number" && Number.isFinite(value))
344
+ return value;
345
+ if (Array.isArray(value))
346
+ return value.map((entry) => normalizeJsonValue2(entry, id, field2));
347
+ if (object(value))
348
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
349
+ key,
350
+ normalizeJsonValue2(entry, id, field2)
351
+ ]));
352
+ throw metadataError(id, `${field2} must be JSON-safe.`);
353
+ }, operation = (value, id, index) => {
354
+ const record = requireObject(value, id, `migration operation ${index} must be an object.`);
355
+ const type = Reflect.get(record, "type");
356
+ const collection = nonEmpty(Reflect.get(record, "collection"), id, `operation ${index}.collection`);
357
+ if (type === "delete-collection")
358
+ return { collection, type };
359
+ if (type === "rename-field")
360
+ return {
361
+ collection,
362
+ from: nonEmpty(Reflect.get(record, "from"), id, `operation ${index}.from`),
363
+ to: nonEmpty(Reflect.get(record, "to"), id, `operation ${index}.to`),
364
+ type
365
+ };
366
+ const field2 = nonEmpty(Reflect.get(record, "field"), id, `operation ${index}.field`);
367
+ if (type === "remove-field")
368
+ return { collection, field: field2, type };
369
+ if (type === "set-default")
370
+ return {
371
+ collection,
372
+ field: field2,
373
+ type,
374
+ value: normalizeJsonValue2(Reflect.get(record, "value"), id, `operation ${index}.value`)
375
+ };
376
+ throw metadataError(id, `operation ${index}.type is not supported.`);
377
+ }, migration = (value, id, index) => {
378
+ const record = requireObject(value, id, `migration ${index} must be an object.`);
379
+ const allowed = new Set(["operations", "toVersion"]);
380
+ const unsupported = Object.keys(record).find((key) => !allowed.has(key));
381
+ if (unsupported)
382
+ throw metadataError(id, `migration ${index}.${unsupported} is not declarative metadata.`);
383
+ const declaredOperations = Reflect.get(record, "operations");
384
+ if (declaredOperations !== undefined && !Array.isArray(declaredOperations))
385
+ throw metadataError(id, `migration ${index}.operations must be an array.`);
386
+ const operations = Array.isArray(declaredOperations) ? declaredOperations : [];
387
+ return {
388
+ operations: operations.map((entry, operationIndex) => operation(entry, id, operationIndex)),
389
+ toVersion: positiveVersion2(Reflect.get(record, "toVersion"), id, `migration ${index}.toVersion`)
390
+ };
391
+ }, localDataPolicy = (value, id) => {
392
+ const record = requireObject(value, id, "localData must be an object.");
393
+ const allowed = new Set([
394
+ "collections",
395
+ "maxBytesPerNamespace",
396
+ "mutations"
397
+ ]);
398
+ const unsupported = Object.keys(record).find((key) => !allowed.has(key));
399
+ if (unsupported)
400
+ throw metadataError(id, `localData.${unsupported} is not supported.`);
401
+ const collectionRules = Reflect.get(record, "collections");
402
+ const mutationRules = Reflect.get(record, "mutations");
403
+ if (collectionRules !== undefined && !Array.isArray(collectionRules))
404
+ throw metadataError(id, "localData.collections must be an array.");
405
+ if (mutationRules !== undefined && !Array.isArray(mutationRules))
406
+ throw metadataError(id, "localData.mutations must be an array.");
407
+ const collections = Array.isArray(collectionRules) ? collectionRules.map((entry, index) => {
408
+ const rule = requireObject(entry, id, `localData.collections[${index}] must be an object.`);
409
+ const allowedRuleKeys = new Set([
410
+ "evictionPriority",
411
+ "match",
412
+ "maxAgeMs",
413
+ "onProtectionUnavailable",
414
+ "persistence",
415
+ "protection",
416
+ "sensitivity"
417
+ ]);
418
+ const unsupportedRuleKey = Object.keys(rule).find((key) => !allowedRuleKeys.has(key));
419
+ if (unsupportedRuleKey)
420
+ throw metadataError(id, `localData.collections[${index}].${unsupportedRuleKey} is not supported.`);
421
+ const match = nonEmpty(Reflect.get(rule, "match"), id, `localData.collections[${index}].match`);
422
+ const persistence = unknownField(rule, "persistence");
423
+ const sensitivity = unknownField(rule, "sensitivity");
424
+ const protection = unknownField(rule, "protection");
425
+ const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
426
+ const evictionPriority = unknownField(rule, "evictionPriority");
427
+ const maxAge = unknownField(rule, "maxAgeMs");
428
+ if (persistence !== undefined && persistence !== "durable" && persistence !== "memory-only")
429
+ throw metadataError(id, `localData.collections[${index}].persistence is invalid.`);
430
+ if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
431
+ throw metadataError(id, `localData.collections[${index}].sensitivity is invalid.`);
432
+ if (protection !== undefined && protection !== "none" && protection !== "required")
433
+ throw metadataError(id, `localData.collections[${index}].protection is invalid.`);
434
+ if (onProtectionUnavailable !== undefined && onProtectionUnavailable !== "error" && onProtectionUnavailable !== "memory-only")
435
+ throw metadataError(id, `localData.collections[${index}].onProtectionUnavailable is invalid.`);
436
+ if (evictionPriority !== undefined && evictionPriority !== "critical" && evictionPriority !== "normal" && evictionPriority !== "disposable")
437
+ throw metadataError(id, `localData.collections[${index}].evictionPriority is invalid.`);
438
+ return {
439
+ match,
440
+ ...sensitivity ? { sensitivity } : {},
441
+ ...persistence ? { persistence } : {},
442
+ ...protection ? { protection } : {},
443
+ ...onProtectionUnavailable ? {
444
+ onProtectionUnavailable
445
+ } : {},
446
+ ...evictionPriority ? { evictionPriority } : {},
447
+ ...maxAge === undefined ? {} : {
448
+ maxAgeMs: positiveVersion2(maxAge, id, `localData.collections[${index}].maxAgeMs`)
449
+ }
450
+ };
451
+ }) : undefined;
452
+ const mutations = Array.isArray(mutationRules) ? mutationRules.map((entry, index) => {
453
+ const rule = requireObject(entry, id, `localData.mutations[${index}] must be an object.`);
454
+ const allowedRuleKeys = new Set([
455
+ "conflict",
456
+ "match",
457
+ "onProtectionUnavailable",
458
+ "persistence",
459
+ "protection",
460
+ "sensitivity"
461
+ ]);
462
+ const unsupportedRuleKey = Object.keys(rule).find((key) => !allowedRuleKeys.has(key));
463
+ if (unsupportedRuleKey)
464
+ throw metadataError(id, `localData.mutations[${index}].${unsupportedRuleKey} is not supported.`);
465
+ const protection = unknownField(rule, "protection");
466
+ const sensitivity = unknownField(rule, "sensitivity");
467
+ const persistence = unknownField(rule, "persistence");
468
+ const onProtectionUnavailable = unknownField(rule, "onProtectionUnavailable");
469
+ const declaredConflict = unknownField(rule, "conflict");
470
+ let conflict;
471
+ if (declaredConflict !== undefined) {
472
+ const conflictRecord = requireObject(declaredConflict, id, `localData.mutations[${index}].conflict must be an object.`);
473
+ const unsupportedConflictKey = Object.keys(conflictRecord).find((key) => key !== "maxAttempts" && key !== "strategy");
474
+ if (unsupportedConflictKey)
475
+ throw metadataError(id, `localData.mutations[${index}].conflict.${unsupportedConflictKey} is not supported.`);
476
+ const strategy = unknownField(conflictRecord, "strategy");
477
+ if (strategy !== "client-wins" && strategy !== "manual" && strategy !== "server-wins")
478
+ throw metadataError(id, `localData.mutations[${index}].conflict.strategy is invalid.`);
479
+ const maxAttempts = unknownField(conflictRecord, "maxAttempts");
480
+ if (maxAttempts !== undefined && strategy !== "client-wins")
481
+ throw metadataError(id, `localData.mutations[${index}].conflict.maxAttempts requires client-wins.`);
482
+ conflict = {
483
+ strategy,
484
+ ...maxAttempts === undefined ? {} : {
485
+ maxAttempts: positiveVersion2(maxAttempts, id, `localData.mutations[${index}].conflict.maxAttempts`)
486
+ }
487
+ };
488
+ }
489
+ if (protection !== undefined && protection !== "none" && protection !== "required")
490
+ throw metadataError(id, `localData.mutations[${index}].protection is invalid.`);
491
+ if (sensitivity !== undefined && sensitivity !== "public" && sensitivity !== "private" && sensitivity !== "secret")
492
+ throw metadataError(id, `localData.mutations[${index}].sensitivity is invalid.`);
493
+ if (onProtectionUnavailable !== undefined && onProtectionUnavailable !== "error" && onProtectionUnavailable !== "memory-only")
494
+ throw metadataError(id, `localData.mutations[${index}].onProtectionUnavailable is invalid.`);
495
+ if (persistence !== undefined && persistence !== "durable" && persistence !== "memory-only")
496
+ throw metadataError(id, `localData.mutations[${index}].persistence is invalid.`);
497
+ return {
498
+ match: nonEmpty(Reflect.get(rule, "match"), id, `localData.mutations[${index}].match`),
499
+ ...conflict ? { conflict } : {},
500
+ ...sensitivity ? { sensitivity } : {},
501
+ ...onProtectionUnavailable ? { onProtectionUnavailable } : {},
502
+ ...persistence ? {
503
+ persistence
504
+ } : {},
505
+ ...protection ? { protection } : {}
506
+ };
507
+ }) : undefined;
508
+ const quota = Reflect.get(record, "maxBytesPerNamespace");
509
+ return {
510
+ ...collections ? { collections } : {},
511
+ ...mutations ? { mutations } : {},
512
+ ...quota === undefined ? {} : {
513
+ maxBytesPerNamespace: positiveVersion2(quota, id, "localData.maxBytesPerNamespace")
514
+ }
515
+ };
516
+ }, component = (id, value) => {
517
+ const record = requireObject(value, id, "localSchema must be an object.");
518
+ const allowed = new Set([
519
+ "localData",
520
+ "migrations",
521
+ "minimumCompatibleVersion",
522
+ "version"
523
+ ]);
524
+ const unsupported = Object.keys(record).find((key) => !allowed.has(key));
525
+ if (unsupported)
526
+ throw metadataError(id, `${unsupported} is not supported.`);
527
+ const version = positiveVersion2(Reflect.get(record, "version"), id, "version");
528
+ const declaredMinimum = Reflect.get(record, "minimumCompatibleVersion");
529
+ const minimumCompatibleVersion = declaredMinimum === undefined ? Math.max(1, version - 2) : positiveVersion2(declaredMinimum, id, "minimumCompatibleVersion");
530
+ const declaredMigrations = Reflect.get(record, "migrations");
531
+ const declaredLocalData = Reflect.get(record, "localData");
532
+ if (declaredMigrations !== undefined && !Array.isArray(declaredMigrations))
533
+ throw metadataError(id, "migrations must be an array.");
534
+ const migrations = Array.isArray(declaredMigrations) ? declaredMigrations : undefined;
535
+ return {
536
+ id,
537
+ ...declaredLocalData === undefined ? {} : { localData: localDataPolicy(declaredLocalData, id) },
538
+ minimumCompatibleVersion,
539
+ ...Array.isArray(migrations) ? {
540
+ migrations: migrations.map((entry, index) => migration(entry, id, index))
541
+ } : {},
542
+ version
543
+ };
544
+ }, dependencyNames = (manifest) => [
545
+ Reflect.get(manifest, "dependencies"),
546
+ Reflect.get(manifest, "optionalDependencies"),
547
+ Reflect.get(manifest, "devDependencies"),
548
+ Reflect.get(manifest, "peerDependencies")
549
+ ].flatMap((dependencies) => object(dependencies) ? Object.keys(dependencies) : []).filter((name, index, names) => names.indexOf(name) === index).sort(), discoverAbsoluteSyncSchema = (projectRoot) => {
550
+ const appManifestPath = join12(resolve10(projectRoot), "package.json");
551
+ const appManifest = manifestAt(appManifestPath);
552
+ if (!appManifest)
553
+ return {
554
+ components: [
555
+ {
556
+ id: "@absolutejs/app",
557
+ minimumCompatibleVersion: 1,
558
+ version: 1
559
+ }
560
+ ],
561
+ sources: []
562
+ };
563
+ const appMetadata = localSchemaMetadata(appManifest);
564
+ const components = [
565
+ appMetadata === undefined ? { id: "@absolutejs/app", minimumCompatibleVersion: 1, version: 1 } : component("@absolutejs/app", appMetadata)
566
+ ];
567
+ const sources = [
568
+ { id: "@absolutejs/app", manifestPath: appManifestPath }
569
+ ];
570
+ for (const name of dependencyNames(appManifest)) {
571
+ const manifestPath = packageManifestPath(projectRoot, name);
572
+ if (!manifestPath)
573
+ continue;
574
+ const manifest = manifestAt(manifestPath);
575
+ if (!manifest)
576
+ continue;
577
+ const metadata = localSchemaMetadata(manifest);
578
+ if (metadata === undefined)
579
+ continue;
580
+ components.push(component(name, metadata));
581
+ sources.push({ id: name, manifestPath });
582
+ }
583
+ components.sort((left, right) => left.id.localeCompare(right.id));
584
+ sources.sort((left, right) => left.id.localeCompare(right.id));
585
+ resolveSyncLocalSchemaComponents({}, { components });
586
+ return { components, sources };
587
+ };
588
+ var init_syncSchema = __esm(() => {
589
+ init_client();
590
+ });
591
+
592
+ // src/mobile/deviceCapabilities.ts
593
+ import { readFileSync as readFileSync4 } from "fs";
594
+ import { extname as extname3, join as join13, relative as relative9, resolve as resolve11 } from "path";
595
+ import ts from "typescript";
596
+ var DEVICES_PACKAGE = "@absolutejs/devices", CAPACITOR_ADAPTER = "@absolutejs/devices-capacitor", SOURCE_GLOB, IGNORED_DIRECTORIES, IDENTIFIER_PATTERN, CAPACITOR_MODULE_PATTERN, CAPACITOR_PACKAGE_PATTERN, 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) => {
597
+ const value = JSON.parse(readFileSync4(path, "utf8"));
598
+ if (!object2(value))
599
+ throw new TypeError(`${path} must contain an object.`);
600
+ return value;
601
+ }, text = (value, field2) => {
602
+ if (typeof value !== "string" || value.length === 0)
603
+ throw new TypeError(`${field2} must be a non-empty string.`);
604
+ return value;
605
+ }, androidPermissions = (value, field2) => {
606
+ if (value === undefined)
607
+ return;
608
+ if (!object2(value))
609
+ throw new TypeError(`${field2} must be an object.`);
610
+ const { permissions } = value;
611
+ if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
612
+ throw new TypeError(`${field2}.permissions must contain Android permission names.`);
613
+ return [...permissions];
614
+ }, iosPrivacyAccessedApis = (value, field2) => {
615
+ if (value === undefined)
616
+ return;
617
+ if (!object2(value))
618
+ throw new TypeError(`${field2} must be an object.`);
619
+ const privacy = {};
620
+ for (const api of IOS_PRIVACY_ACCESSED_APIS) {
621
+ const reasons = value[api];
622
+ if (reasons === undefined)
623
+ continue;
624
+ const supported = IOS_PRIVACY_ACCESSED_API_REASONS[api];
625
+ if (!Array.isArray(reasons) || reasons.length === 0 || !reasons.every((reason) => typeof reason === "string" && supported.has(reason)))
626
+ throw new TypeError(`${field2} contains an unsupported API or reason.`);
627
+ privacy[api] = [...reasons];
628
+ }
629
+ if (Object.keys(value).some((api) => !IOS_PRIVACY_ACCESSED_APIS.some((known) => known === api)))
630
+ throw new TypeError(`${field2} contains an unsupported API or reason.`);
631
+ return privacy;
632
+ }, iosNativeRequirements = (value, field2) => {
633
+ if (value === undefined)
634
+ return;
635
+ if (!object2(value))
636
+ throw new TypeError(`${field2} must be an object.`);
637
+ const {
638
+ privacyAccessedApis,
639
+ pushNotifications,
640
+ systemBars,
641
+ usageDescriptions
642
+ } = value;
643
+ if (pushNotifications !== undefined && pushNotifications !== true)
644
+ throw new TypeError(`${field2}.pushNotifications must be true.`);
645
+ if (systemBars !== undefined && systemBars !== true)
646
+ throw new TypeError(`${field2}.systemBars must be true.`);
647
+ if (usageDescriptions !== undefined && (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose))))
648
+ throw new TypeError(`${field2}.usageDescriptions contains an unsupported purpose.`);
649
+ const privacy = iosPrivacyAccessedApis(privacyAccessedApis, `${field2}.privacyAccessedApis`);
650
+ return {
651
+ ...privacy === undefined ? {} : { privacyAccessedApis: privacy },
652
+ ...pushNotifications === true ? { pushNotifications: true } : {},
653
+ ...systemBars === true ? { systemBars: true } : {},
654
+ ...usageDescriptions === undefined ? {} : { usageDescriptions: [...usageDescriptions] }
655
+ };
656
+ }, parseProvider = (name, value) => {
657
+ if (!IDENTIFIER_PATTERN.test(name))
658
+ throw new TypeError("Device capability names must be identifiers.");
659
+ if (!object2(value))
660
+ throw new TypeError(`Device capability ${name} must be an object.`);
661
+ const factory = text(value.factory, `${name}.factory`);
662
+ const module = text(value.module, `${name}.module`);
663
+ if (!IDENTIFIER_PATTERN.test(factory))
664
+ throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
665
+ if (!CAPACITOR_MODULE_PATTERN.test(module))
666
+ throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
667
+ if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
668
+ throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
669
+ let native;
670
+ const { native: nativeMetadata } = value;
671
+ if (nativeMetadata !== undefined) {
672
+ if (!object2(nativeMetadata))
673
+ throw new TypeError(`${name}.native must be an object.`);
674
+ const { android, ios } = nativeMetadata;
675
+ const permissions = androidPermissions(android, `${name}.native.android`);
676
+ const iosRequirements = iosNativeRequirements(ios, `${name}.native.ios`);
677
+ native = {
678
+ ...permissions === undefined ? {} : { android: { permissions } },
679
+ ...iosRequirements === undefined ? {} : { ios: iosRequirements }
680
+ };
681
+ }
682
+ return {
683
+ factory,
684
+ module,
685
+ ...native === undefined ? {} : { native },
686
+ packages: [...value.packages]
687
+ };
688
+ }, absoluteDeviceNativeRequirements = (plan) => {
689
+ const privacy = plan.capabilities.reduce((requirements, name) => {
690
+ for (const api of IOS_PRIVACY_ACCESSED_APIS) {
691
+ const reasons = plan.providers[name]?.native?.ios?.privacyAccessedApis?.[api] ?? [];
692
+ if (reasons.length === 0)
693
+ continue;
694
+ const current = requirements[api] ?? new Set;
695
+ for (const reason of reasons)
696
+ current.add(reason);
697
+ requirements[api] = current;
698
+ }
699
+ return requirements;
700
+ }, {});
701
+ return {
702
+ androidPermissions: [
703
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
704
+ ].sort(),
705
+ iosPrivacyAccessedApis: IOS_PRIVACY_ACCESSED_APIS.flatMap((api) => {
706
+ const reasons = privacy[api];
707
+ return reasons ? [{ api, reasons: [...reasons].sort() }] : [];
708
+ }),
709
+ iosPushNotifications: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.pushNotifications === true),
710
+ iosSystemBars: plan.capabilities.some((name) => plan.providers[name]?.native?.ios?.systemBars === true),
711
+ iosUsageDescriptions: [
712
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
713
+ ].sort()
714
+ };
715
+ }, loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
716
+ const path = join13(resolve11(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
717
+ const manifest = readJson(path);
718
+ const { absolutejs } = manifest;
719
+ const devices = object2(absolutejs) ? absolutejs.devices : undefined;
720
+ if (!object2(devices) || devices.format !== 1 || devices.provider !== "capacitor" || !object2(devices.capabilities))
721
+ throw new TypeError(`${CAPACITOR_ADAPTER} does not publish supported capability metadata.`);
722
+ const entries = Object.entries(devices.capabilities).map(([name, provider]) => ({
723
+ name,
724
+ provider: parseProvider(name, provider)
725
+ }));
726
+ return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider }) => [name, provider]));
727
+ }, isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment)), importedCapabilities = (source, file) => {
728
+ const names = new Set;
729
+ const namespaces = new Set;
730
+ const visit = (node) => {
731
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
732
+ const bindings = node.importClause?.namedBindings;
733
+ if (bindings && ts.isNamedImports(bindings)) {
734
+ for (const element of bindings.elements)
735
+ if (!element.isTypeOnly)
736
+ names.add((element.propertyName ?? element.name).text);
737
+ }
738
+ if (bindings && ts.isNamespaceImport(bindings))
739
+ namespaces.add(bindings.name.text);
740
+ }
741
+ if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
742
+ for (const element of node.exportClause.elements)
743
+ if (!element.isTypeOnly)
744
+ names.add((element.propertyName ?? element.name).text);
745
+ }
746
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
747
+ names.add(node.name.text);
748
+ ts.forEachChild(node, visit);
749
+ };
750
+ const extension = extname3(file).toLowerCase();
751
+ const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
752
+ for (const [index, script] of sources.entries())
753
+ visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
754
+ return names;
755
+ }, assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
756
+ const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
757
+ const mismatched = plan.requiredPackages.filter((spec) => {
758
+ const separator = spec.lastIndexOf("@");
759
+ const packageName = spec.slice(0, separator);
760
+ if (missing.includes(spec))
761
+ return false;
762
+ try {
763
+ return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
764
+ } catch {
765
+ return true;
766
+ }
767
+ });
768
+ const unmet = [...missing, ...mismatched];
769
+ if (unmet.length > 0)
770
+ throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
771
+ }, directAbsoluteProjectPackages = (projectRoot) => {
772
+ const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
773
+ const packages = new Set;
774
+ for (const field2 of ["dependencies", "devDependencies"]) {
775
+ const dependencies = manifest[field2];
776
+ if (object2(dependencies))
777
+ for (const name of Object.keys(dependencies))
778
+ packages.add(name);
779
+ }
780
+ return packages;
781
+ }, discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
782
+ const root = resolve11(projectRoot);
783
+ const known = new Set(Object.keys(providers));
784
+ const capabilities = new Set;
785
+ for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
786
+ const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
787
+ if (isIgnored(portable))
788
+ continue;
789
+ const source = readFileSync4(resolve11(root, portable), "utf8");
790
+ for (const name of importedCapabilities(source, portable))
791
+ if (known.has(name))
792
+ capabilities.add(name);
793
+ }
794
+ return [...capabilities].sort();
795
+ }, missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
796
+ const packageName = spec.slice(0, spec.lastIndexOf("@"));
797
+ return !directPackages.has(packageName);
798
+ }), projectImportsAbsoluteDeviceCapability = (projectRoot, capability) => {
799
+ const root = resolve11(projectRoot);
800
+ for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
801
+ const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
802
+ if (isIgnored(portable))
803
+ continue;
804
+ const source = readFileSync4(resolve11(root, portable), "utf8");
805
+ if (importedCapabilities(source, portable).has(capability))
806
+ return true;
807
+ }
808
+ return false;
809
+ }, resolveAbsoluteDeviceCapabilityPlan = (projectRoot) => {
810
+ const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot);
811
+ const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
812
+ const providers = {};
813
+ for (const name of capabilities) {
814
+ const provider = allProviders[name];
815
+ if (provider)
816
+ providers[name] = provider;
817
+ }
818
+ return {
819
+ capabilities,
820
+ providers,
821
+ requiredPackages: [
822
+ ...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
823
+ ].sort()
824
+ };
825
+ };
826
+ var init_deviceCapabilities = __esm(() => {
827
+ SOURCE_GLOB = new Bun.Glob("**/*.{js,jsx,ts,tsx,svelte,vue}");
828
+ IGNORED_DIRECTORIES = new Set([
829
+ ".absolutejs",
830
+ ".git",
831
+ ".test-builds",
832
+ ".test-shards",
833
+ "build",
834
+ "dist",
835
+ "node_modules",
836
+ "test",
837
+ "tests"
838
+ ]);
839
+ IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
840
+ CAPACITOR_MODULE_PATTERN = /^@absolutejs\/devices-capacitor\/[a-z][a-z0-9-]*$/u;
841
+ CAPACITOR_PACKAGE_PATTERN = /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u;
842
+ ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
843
+ IOS_USAGE_DESCRIPTIONS = new Set([
844
+ "camera",
845
+ "location-always",
846
+ "location-when-in-use",
847
+ "photo-library",
848
+ "photo-library-add"
849
+ ]);
850
+ IOS_PRIVACY_ACCESSED_API_REASONS = {
851
+ NSPrivacyAccessedAPICategoryFileTimestamp: new Set(["C617.1"])
852
+ };
853
+ IOS_PRIVACY_ACCESSED_APIS = [
854
+ "NSPrivacyAccessedAPICategoryFileTimestamp"
855
+ ];
856
+ });
857
+
162
858
  // src/mobile/artifactStore.ts
163
859
  import { createHash as createHash2 } from "crypto";
164
860
  import {
@@ -249,13 +945,19 @@ var parseCompatibilityPage = (value) => {
249
945
  if (!isCanonicalRecord(value) || !isPageFramework(value.framework)) {
250
946
  throw new TypeError("Compatibility artifact contains an invalid page.");
251
947
  }
948
+ const styleBundleHash = typeof value.styleBundleHash === "string" ? value.styleBundleHash : undefined;
949
+ const styleBundlePath = typeof value.styleBundlePath === "string" ? value.styleBundlePath : undefined;
950
+ if (Boolean(styleBundleHash) !== Boolean(styleBundlePath)) {
951
+ throw new TypeError("Compatibility page style hash and path must be provided together.");
952
+ }
252
953
  return {
253
954
  bundleHash: readString(value.bundleHash, "page.bundleHash"),
254
955
  bundlePath: readString(value.bundlePath, "page.bundlePath"),
255
956
  contract: readString(value.contract, "page.contract"),
256
957
  framework: value.framework,
257
958
  pageId: readString(value.pageId, "page.pageId"),
258
- propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash")
959
+ propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash"),
960
+ ...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
259
961
  };
260
962
  };
261
963
  var parseCompatibilityRoute = (value) => {
@@ -287,14 +989,23 @@ var validateProducerModule = (module) => {
287
989
  }
288
990
  return module;
289
991
  };
290
- var normalizePage = (page) => ({
291
- bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
292
- bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
293
- contract: requireNonEmpty(page.contract, "page.contract"),
294
- framework: page.framework,
295
- pageId: requireNonEmpty(page.pageId, "page.pageId"),
296
- propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash")
297
- });
992
+ var normalizePage = (page) => {
993
+ if (Boolean(page.styleBundleHash) !== Boolean(page.styleBundlePath)) {
994
+ throw new TypeError("Compatibility page style hash and path must be provided together.");
995
+ }
996
+ return {
997
+ bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
998
+ bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
999
+ contract: requireNonEmpty(page.contract, "page.contract"),
1000
+ framework: page.framework,
1001
+ pageId: requireNonEmpty(page.pageId, "page.pageId"),
1002
+ propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash"),
1003
+ ...page.styleBundleHash && page.styleBundlePath ? {
1004
+ styleBundleHash: requireNonEmpty(page.styleBundleHash, "page.styleBundleHash"),
1005
+ styleBundlePath: requireNonEmpty(page.styleBundlePath, "page.styleBundlePath")
1006
+ } : {}
1007
+ };
1008
+ };
298
1009
  var normalizeRoute = (route) => {
299
1010
  if (!route.pattern.startsWith("/")) {
300
1011
  throw new TypeError("route.pattern must start with /.");
@@ -1059,11 +1770,11 @@ var hashNativeTree = async (root, label, ignorePublicBundle) => {
1059
1770
  const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
1060
1771
  return createHash3("sha256").update(records.join("")).digest("hex");
1061
1772
  };
1062
- var fingerprintAbsoluteAndroidNativeProject = async (project) => {
1773
+ var fingerprintAbsoluteAndroidNativeProject = async (project, options = {}) => {
1063
1774
  const { dependencies } = await nativeDependencySources(project.nativeDirectory);
1064
1775
  const roots = [
1065
1776
  {
1066
- ignorePublicBundle: true,
1777
+ ignorePublicBundle: options.includePublicBundle !== true,
1067
1778
  label: "android",
1068
1779
  source: project.nativeDirectory
1069
1780
  },
@@ -1358,6 +2069,92 @@ var buildAbsoluteAndroidRelease = async (options) => {
1358
2069
  };
1359
2070
  return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
1360
2071
  };
2072
+ // src/mobile/androidUpgradeConformance.ts
2073
+ import { performance as performance2 } from "perf_hooks";
2074
+ var captureCommand3 = async (command) => {
2075
+ const process2 = Bun.spawn(command, { stderr: "pipe", stdout: "pipe" });
2076
+ const [exitCode, stderr, stdout] = await Promise.all([
2077
+ process2.exited,
2078
+ new Response(process2.stderr).text(),
2079
+ new Response(process2.stdout).text()
2080
+ ]);
2081
+ return { exitCode, stderr, stdout };
2082
+ };
2083
+ var field = (output, name) => new RegExp(`^\\s*${name}=([^\\r\\n]+)$`, "mu").exec(output)?.[1]?.trim();
2084
+ var inspectAbsoluteAndroidInstalledApp = async (adb, serial, appId, run = captureCommand3) => {
2085
+ const result = await run([
2086
+ adb,
2087
+ "-s",
2088
+ serial,
2089
+ "shell",
2090
+ "dumpsys",
2091
+ "package",
2092
+ appId
2093
+ ]);
2094
+ if (result.exitCode !== 0)
2095
+ throw new Error(`Could not inspect installed Android app ${appId}: ${result.stderr.trim() || result.stdout.trim()}`);
2096
+ const installed = parseAbsoluteAndroidInstalledApp(appId, result.stdout);
2097
+ if (!installed)
2098
+ throw new Error(`Android app ${appId} is not installed on ${serial}.`);
2099
+ return installed;
2100
+ };
2101
+ var parseAbsoluteAndroidInstalledApp = (appId, output) => {
2102
+ const packageName = /^\s*Package \[([^\]]+)\]/mu.exec(output)?.[1];
2103
+ if (packageName !== appId && !output.includes(`package:${appId}`))
2104
+ return null;
2105
+ const versionCodeText = /^\s*versionCode=(\d+)/mu.exec(output)?.[1];
2106
+ return {
2107
+ appId,
2108
+ ...field(output, "dataDir") ? { dataDirectory: field(output, "dataDir") } : {},
2109
+ ...field(output, "firstInstallTime") ? { firstInstallTime: field(output, "firstInstallTime") } : {},
2110
+ ...field(output, "lastUpdateTime") ? { lastUpdateTime: field(output, "lastUpdateTime") } : {},
2111
+ ...field(output, "userId") || field(output, "appId") ? { uid: field(output, "userId") ?? field(output, "appId") } : {},
2112
+ ...versionCodeText ? { versionCode: Number(versionCodeText) } : {},
2113
+ ...field(output, "versionName") ? { versionName: field(output, "versionName") } : {}
2114
+ };
2115
+ };
2116
+ var unchanged = (name, before, after) => {
2117
+ if (!before || !after)
2118
+ throw new Error(`Android did not report ${name} for the upgrade proof.`);
2119
+ if (before !== after)
2120
+ throw new Error(`Android ${name} changed during the in-place upgrade.`);
2121
+ };
2122
+ var runAbsoluteAndroidUpgradeConformance = async (options) => {
2123
+ const run = options.run ?? captureCommand3;
2124
+ const startedAt = performance2.now();
2125
+ const before = await inspectAbsoluteAndroidInstalledApp(options.adb, options.serial, options.appId, run);
2126
+ const installStartedAt = performance2.now();
2127
+ const installed = await run([
2128
+ options.adb,
2129
+ "-s",
2130
+ options.serial,
2131
+ "install",
2132
+ "-r",
2133
+ options.apkPath
2134
+ ]);
2135
+ const installMs = Math.round(performance2.now() - installStartedAt);
2136
+ if (installed.exitCode !== 0 || !installed.stdout.includes("Success"))
2137
+ throw new Error(`Android in-place upgrade failed: ${installed.stderr.trim() || installed.stdout.trim()}`);
2138
+ const after = await inspectAbsoluteAndroidInstalledApp(options.adb, options.serial, options.appId, run);
2139
+ unchanged("application UID", before.uid, after.uid);
2140
+ unchanged("data directory", before.dataDirectory, after.dataDirectory);
2141
+ unchanged("first install timestamp", before.firstInstallTime, after.firstInstallTime);
2142
+ if (before.versionCode !== undefined && after.versionCode !== undefined && after.versionCode <= before.versionCode)
2143
+ throw new Error(`Android versionCode did not increase (${before.versionCode} -> ${after.versionCode}).`);
2144
+ await options.afterInstall?.(after);
2145
+ const state = await options.verifyState();
2146
+ const compatibilityPass = !options.compatibility || options.compatibility.nPlusOne === "compatible" && options.compatibility.nPlusTwo === "compatible" && options.compatibility.nPlusThree === "upgrade-required" && options.compatibility.rollback === "compatible";
2147
+ const outcome = Object.values(state).every(Boolean) && compatibilityPass ? "pass" : "fail";
2148
+ return {
2149
+ after,
2150
+ before,
2151
+ ...options.compatibility ? { compatibility: options.compatibility } : {},
2152
+ durationMs: Math.round(performance2.now() - startedAt),
2153
+ installMs,
2154
+ outcome,
2155
+ state
2156
+ };
2157
+ };
1361
2158
  // src/mobile/iosRelease.ts
1362
2159
  import { createHash as createHash5 } from "crypto";
1363
2160
  import {
@@ -2533,118 +3330,696 @@ var createAbsoluteIosNativeWatcher = async (options) => {
2533
3330
  return { close };
2534
3331
  };
2535
3332
  var isAbsoluteIosNativeRootInput = (path) => ROOT_NATIVE_INPUTS.has(basename(path));
2536
- // src/mobile/associationFiles.ts
3333
+ // src/mobile/remoteMacProtocol.ts
3334
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
3335
+ import { chmod, mkdir as mkdir6, readFile as readFile8, rename as rename7, writeFile as writeFile7 } from "fs/promises";
3336
+ import { homedir as homedir2 } from "os";
2537
3337
  import {
2538
- access as access7,
2539
- mkdir as mkdir6,
2540
- readFile as readFile8,
2541
- rename as rename7,
2542
- rm as rm6,
2543
- writeFile as writeFile7
2544
- } from "fs/promises";
2545
- import { resolve as resolve7 } from "path";
2546
- import { Elysia } from "elysia";
3338
+ dirname as dirname5,
3339
+ isAbsolute as isAbsolute5,
3340
+ join as join7,
3341
+ posix,
3342
+ relative as relative6,
3343
+ resolve as resolvePath2,
3344
+ sep as sep5
3345
+ } from "path";
2547
3346
 
2548
- // src/mobile/config.ts
2549
- import { resolve as resolve6 } from "path";
2550
- var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
2551
- var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
2552
- var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
2553
- var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
2554
- var HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
2555
- var resolveProjectPath = (projectRoot, value, field) => {
2556
- const root = resolve6(projectRoot);
2557
- const path = resolve6(root, value);
2558
- if (path !== root && !path.startsWith(`${root}/`)) {
2559
- throw new TypeError(`${field} must remain inside the project root.`);
3347
+ // src/mobile/remoteMacWire.ts
3348
+ var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t";
3349
+ var ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
3350
+
3351
+ // src/mobile/remoteMacProtocol.ts
3352
+ var PROFILE_FORMAT = 1;
3353
+ var REMOTE_STDIN_FLUSH_ATTEMPTS = 3;
3354
+ var PROFILE_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
3355
+ var SSH_DESTINATION = /^(?:[A-Za-z0-9._-]+@)?[A-Za-z0-9._:-]+$/u;
3356
+ var defaultProfilePath = () => join7(homedir2(), ".absolutejs", "mobile", "remote-macs.json");
3357
+ var emptyStore = () => ({
3358
+ format: PROFILE_FORMAT,
3359
+ profiles: {}
3360
+ });
3361
+ var loadStore = async (path = defaultProfilePath()) => {
3362
+ try {
3363
+ const parsed = JSON.parse(await readFile8(path, "utf8"));
3364
+ if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
3365
+ throw new Error("Unsupported remote Mac profile format.");
3366
+ for (const [key, profile] of Object.entries(parsed.profiles)) {
3367
+ if (typeof profile !== "object" || profile === null || validateAbsoluteRemoteMacProfileName(key) !== key || profile.name !== key || validateAbsoluteSshDestination(profile.destination) !== profile.destination || validatePort(profile.port) !== profile.port || typeof profile.createdAt !== "string" || !profile.createdAt || typeof profile.bunPath !== "string" || !profile.bunPath.startsWith("/") || /[\r\n\0]/u.test(profile.bunPath) || typeof profile.workspaceRoot !== "string" || !profile.workspaceRoot.startsWith("/") || profile.workspaceRoot === "/" || /[\r\n\0]/u.test(profile.workspaceRoot) || typeof profile.xcodeVersion !== "string" || !profile.xcodeVersion.startsWith("Xcode "))
3368
+ throw new Error(`Remote Mac profile ${JSON.stringify(key)} is invalid.`);
3369
+ }
3370
+ if (parsed.defaultProfile !== undefined && !parsed.profiles[parsed.defaultProfile])
3371
+ throw new Error("The default remote Mac profile does not exist.");
3372
+ return parsed;
3373
+ } catch (error) {
3374
+ if (error.code === "ENOENT")
3375
+ return emptyStore();
3376
+ throw error;
2560
3377
  }
2561
- return path;
2562
3378
  };
2563
- var requireText = (value, field) => {
2564
- const normalized = value.trim();
2565
- if (!normalized)
2566
- throw new TypeError(`${field} must not be empty.`);
2567
- return normalized;
3379
+ var saveStore = async (store, path = defaultProfilePath()) => {
3380
+ await mkdir6(dirname5(path), { recursive: true });
3381
+ const temporary = `${path}.${randomUUID3()}.tmp`;
3382
+ await writeFile7(temporary, `${JSON.stringify(store, null, 2)}
3383
+ `, {
3384
+ mode: 384
3385
+ });
3386
+ await rename7(temporary, path);
3387
+ await chmod(path, 384);
2568
3388
  };
2569
- var normalizeEntry = (entry) => {
2570
- const normalized = requireText(entry ?? "/", "mobile.entry");
2571
- if (!normalized.startsWith("/") || normalized.startsWith("//")) {
2572
- throw new TypeError("mobile.entry must be an absolute application path.");
2573
- }
3389
+ var validateAbsoluteRemoteMacProfileName = (name) => {
3390
+ const normalized = name.trim().toLowerCase();
3391
+ if (!PROFILE_NAME.test(normalized))
3392
+ throw new TypeError("Remote Mac profile names must use 1-64 lowercase letters, digits, dots, dashes, or underscores.");
2574
3393
  return normalized;
2575
3394
  };
2576
- var normalizeProductionOrigin = (value) => {
2577
- const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
2578
- if (parsed.protocol !== "https:") {
2579
- throw new TypeError("mobile.server.productionOrigin must use HTTPS in production.");
2580
- }
2581
- if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
2582
- throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
2583
- }
2584
- return parsed.origin;
2585
- };
2586
- var normalizePlatforms = (platforms) => {
2587
- const normalized = [
2588
- ...new Set(platforms ?? ["ios", "android"])
2589
- ];
2590
- if (normalized.length === 0) {
2591
- throw new TypeError("mobile.platforms must contain at least one platform.");
2592
- }
3395
+ var validateAbsoluteSshDestination = (destination) => {
3396
+ const normalized = destination.trim();
3397
+ if (!SSH_DESTINATION.test(normalized) || normalized.startsWith("-"))
3398
+ throw new TypeError("Remote Mac SSH destination must be a host, SSH alias, or user@host without command-line options.");
2593
3399
  return normalized;
2594
3400
  };
2595
- var normalizeHosts = (hosts, productionOrigin) => {
2596
- const normalizeHostname = (host) => {
2597
- const value = requireText(host, "mobile.deepLinks.hosts").toLowerCase();
2598
- if (!HOSTNAME_PATTERN.test(value)) {
2599
- throw new TypeError("mobile.deepLinks.hosts entries must be valid hostnames without ports, wildcards, or paths.");
2600
- }
2601
- return value;
2602
- };
2603
- const normalized = new Set([
2604
- normalizeHostname(new URL(productionOrigin).hostname)
3401
+ var validatePort = (port) => {
3402
+ if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535))
3403
+ throw new TypeError("Remote Mac SSH port must be between 1 and 65535.");
3404
+ return port;
3405
+ };
3406
+ var shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
3407
+ var absoluteRemoteMacSshBase = (profile, options = {}) => [
3408
+ "ssh",
3409
+ "-o",
3410
+ "BatchMode=yes",
3411
+ "-o",
3412
+ "ConnectTimeout=10",
3413
+ "-o",
3414
+ "ServerAliveInterval=15",
3415
+ "-o",
3416
+ "ServerAliveCountMax=3",
3417
+ "-o",
3418
+ `StrictHostKeyChecking=${options.acceptNew ? "accept-new" : "yes"}`,
3419
+ ...profile.port ? ["-p", String(profile.port)] : [],
3420
+ profile.destination
3421
+ ];
3422
+ var localCapture = async (command) => {
3423
+ const process2 = Bun.spawn(command, {
3424
+ stderr: "pipe",
3425
+ stdin: "ignore",
3426
+ stdout: "pipe"
3427
+ });
3428
+ const [exitCode, stdout, stderr] = await Promise.all([
3429
+ process2.exited,
3430
+ new Response(process2.stdout).text(),
3431
+ new Response(process2.stderr).text()
2605
3432
  ]);
2606
- for (const host of hosts ?? []) {
2607
- normalized.add(normalizeHostname(host));
2608
- }
2609
- return [...normalized].sort();
3433
+ return { exitCode, stderr, stdout };
2610
3434
  };
2611
- var normalizeAppleAppIdPrefix = (value) => {
2612
- if (value === undefined)
2613
- return;
2614
- const normalized = requireText(value, "mobile.deepLinks.apple.appIdPrefix").toUpperCase();
2615
- if (!APPLE_APP_ID_PREFIX_PATTERN.test(normalized)) {
2616
- throw new TypeError("mobile.deepLinks.apple.appIdPrefix must contain ten letters or digits.");
2617
- }
2618
- return normalized;
3435
+ var defaultTransport = {
3436
+ capture: localCapture,
3437
+ spawn: (command, options) => Bun.spawn(command, {
3438
+ signal: options.signal,
3439
+ stderr: "pipe",
3440
+ stdin: "pipe",
3441
+ stdout: "pipe"
3442
+ })
2619
3443
  };
2620
- var normalizeIosVersion = (value) => {
2621
- if (value === undefined)
3444
+ var requireRemoteSuccess = (result, label) => {
3445
+ if (result.exitCode !== 0)
3446
+ throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim() || `status ${result.exitCode}`}`);
3447
+ return result.stdout.trim();
3448
+ };
3449
+ var getAbsoluteRemoteMacProfile = async (name, profilePath) => {
3450
+ const store = await loadStore(profilePath);
3451
+ const selected = name ?? process.env.ABSOLUTE_IOS_REMOTE ?? store.defaultProfile;
3452
+ if (!selected)
2622
3453
  return;
2623
- const normalized = requireText(value, "mobile.ios.version");
2624
- if (!/^\d+(?:\.\d+){0,2}$/u.test(normalized)) {
2625
- throw new TypeError("mobile.ios.version must contain one to three dot-separated integer components, for example 1.4.0.");
3454
+ const profile = store.profiles[selected];
3455
+ if (!profile)
3456
+ throw new Error(`Remote Mac profile ${JSON.stringify(selected)} was not found.`);
3457
+ return profile;
3458
+ };
3459
+ var inspectAbsoluteRemoteMac = async (destination, options = {}) => {
3460
+ const profile = {
3461
+ destination: validateAbsoluteSshDestination(destination),
3462
+ port: validatePort(options.port)
3463
+ };
3464
+ const capture = options.transport?.capture ?? defaultTransport.capture;
3465
+ const command = [
3466
+ ...absoluteRemoteMacSshBase(profile, {
3467
+ acceptNew: options.acceptNew === true
3468
+ }),
3469
+ "/bin/sh -lc",
3470
+ shellQuote(`bun_path="$(command -v bun || true)"; if [ -z "$bun_path" ] && [ -x "$HOME/.bun/bin/bun" ]; then bun_path="$HOME/.bun/bin/bun"; fi; printf '%s\\n' "$(uname -s)" "$HOME" "$bun_path" "$(/usr/bin/xcodebuild -version 2>/dev/null | tr '\\n' ' ' || true)"`)
3471
+ ];
3472
+ const lines = requireRemoteSuccess(await capture(command), "Remote Mac handshake").split(/\r?\n/u);
3473
+ const [operatingSystem, home, bunPath, xcodeVersion] = lines;
3474
+ if (operatingSystem !== "Darwin")
3475
+ throw new Error("The SSH target is not a Mac.");
3476
+ if (!home?.startsWith("/") || !bunPath?.startsWith("/"))
3477
+ throw new Error("The remote Mac must have Bun installed and available to SSH.");
3478
+ if (!xcodeVersion?.startsWith("Xcode "))
3479
+ throw new Error("The remote Mac must have full Xcode installed and selected.");
3480
+ return { bunPath, home, os: operatingSystem, xcodeVersion };
3481
+ };
3482
+ var listAbsoluteRemoteMacProfiles = async (profilePath) => {
3483
+ const store = await loadStore(profilePath);
3484
+ return {
3485
+ defaultProfile: store.defaultProfile,
3486
+ profiles: Object.values(store.profiles).sort((left, right) => left.name.localeCompare(right.name))
3487
+ };
3488
+ };
3489
+ var pairAbsoluteRemoteMac = async (options) => {
3490
+ const name = validateAbsoluteRemoteMacProfileName(options.name);
3491
+ const destination = validateAbsoluteSshDestination(options.destination);
3492
+ const port = validatePort(options.port);
3493
+ const inspection = await inspectAbsoluteRemoteMac(destination, {
3494
+ acceptNew: true,
3495
+ port,
3496
+ transport: options.transport
3497
+ });
3498
+ const workspaceRoot = options.workspaceRoot ? options.workspaceRoot.trim() : posix.join(inspection.home, ".absolutejs", "remote-ios");
3499
+ if (!workspaceRoot.startsWith("/") || workspaceRoot === "/" || /[\r\n\0]/u.test(workspaceRoot))
3500
+ throw new TypeError("Remote Mac workspace must be an absolute macOS path.");
3501
+ const profile = {
3502
+ bunPath: inspection.bunPath,
3503
+ createdAt: new Date().toISOString(),
3504
+ destination,
3505
+ name,
3506
+ ...port ? { port } : {},
3507
+ workspaceRoot,
3508
+ xcodeVersion: inspection.xcodeVersion
3509
+ };
3510
+ const store = await loadStore(options.profilePath);
3511
+ store.profiles[name] = profile;
3512
+ store.defaultProfile = name;
3513
+ await saveStore(store, options.profilePath);
3514
+ return profile;
3515
+ };
3516
+ var removeAbsoluteRemoteMacProfile = async (name, profilePath) => {
3517
+ const normalized = validateAbsoluteRemoteMacProfileName(name);
3518
+ const store = await loadStore(profilePath);
3519
+ if (!store.profiles[normalized])
3520
+ return false;
3521
+ delete store.profiles[normalized];
3522
+ if (store.defaultProfile === normalized) {
3523
+ const [nextDefault] = Object.keys(store.profiles).sort();
3524
+ store.defaultProfile = nextDefault;
2626
3525
  }
2627
- return normalized;
3526
+ await saveStore(store, profilePath);
3527
+ return true;
2628
3528
  };
2629
- var normalizeCertificateFingerprints = (values) => [
2630
- ...new Set((values ?? []).map((value) => requireText(value, "mobile.deepLinks.android.sha256CertificateFingerprints").replaceAll(":", "").toUpperCase()).map((value) => {
2631
- if (!CERTIFICATE_FINGERPRINT_PATTERN.test(value)) {
2632
- throw new TypeError("mobile.deepLinks.android.sha256CertificateFingerprints entries must be SHA-256 certificate fingerprints.");
3529
+ var projectIdentity = (projectRoot, appId) => createHash7("sha256").update(`${resolvePath2(projectRoot)}\x00${appId}`).digest("hex").slice(0, 20);
3530
+ var createAbsoluteRemoteIosDevProject = (config, projectRoot, profile) => ({
3531
+ cap: join7(resolvePath2(projectRoot), "node_modules", ".bin", "cap"),
3532
+ config,
3533
+ nativeDirectory: join7(config.nativeProjectDirectory, "ios"),
3534
+ profile,
3535
+ projectRoot: resolvePath2(projectRoot),
3536
+ remote: true,
3537
+ remoteProjectRoot: posix.join(profile.workspaceRoot, "projects", projectIdentity(projectRoot, config.appId), "current"),
3538
+ xcodebuild: "remote:xcodebuild",
3539
+ xcrun: "remote:xcrun"
3540
+ });
3541
+ var installAbsoluteRemoteMacAgent = async (project) => {
3542
+ const artifact = await materializeAbsoluteRemoteMacAgent(project.projectRoot);
3543
+ const directory = posix.join(project.profile.workspaceRoot, "agents", `protocol-${ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION}`, artifact.sha256);
3544
+ const remotePath = posix.join(directory, "agent.js");
3545
+ const verifyScript = `test -f ${shellQuote(remotePath)} && ` + `test "$(shasum -a 256 ${shellQuote(remotePath)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`;
3546
+ const verified = await defaultTransport.capture([
3547
+ ...absoluteRemoteMacSshBase(project.profile),
3548
+ "/bin/sh -lc",
3549
+ shellQuote(verifyScript)
3550
+ ]);
3551
+ if (verified.exitCode === 0)
3552
+ return { ...artifact, remotePath, uploaded: false };
3553
+ const temporary = posix.join(directory, `.agent-${randomUUID3()}.tmp`);
3554
+ const installScript = [
3555
+ "set -eu",
3556
+ "umask 077",
3557
+ `mkdir -p ${shellQuote(directory)}`,
3558
+ `cat > ${shellQuote(temporary)}`,
3559
+ `test "$(shasum -a 256 ${shellQuote(temporary)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`,
3560
+ `chmod 600 ${shellQuote(temporary)}`,
3561
+ `mv ${shellQuote(temporary)} ${shellQuote(remotePath)}`
3562
+ ].join("; ");
3563
+ const upload = Bun.spawn([
3564
+ ...absoluteRemoteMacSshBase(project.profile),
3565
+ "/bin/sh -lc",
3566
+ shellQuote(installScript)
3567
+ ], {
3568
+ stderr: "pipe",
3569
+ stdin: Bun.file(artifact.path),
3570
+ stdout: "pipe"
3571
+ });
3572
+ const [exitCode, stderr] = await Promise.all([
3573
+ upload.exited,
3574
+ new Response(upload.stderr).text()
3575
+ ]);
3576
+ if (exitCode !== 0)
3577
+ throw new Error(`Remote Mac agent installation failed: ${stderr.trim() || `status ${exitCode}`}`);
3578
+ return { ...artifact, remotePath, uploaded: true };
3579
+ };
3580
+ var materializeAbsoluteRemoteMacAgent = async (projectRoot) => {
3581
+ const shippedCandidates = [
3582
+ join7(import.meta.dir, "remoteMacAgentEntry.js"),
3583
+ join7(import.meta.dir, "..", "mobile", "remoteMacAgentEntry.js")
3584
+ ];
3585
+ let path;
3586
+ for (const candidate of shippedCandidates) {
3587
+ if (await Bun.file(candidate).exists()) {
3588
+ path = candidate;
3589
+ break;
2633
3590
  }
2634
- return value.match(/.{2}/g)?.join(":") ?? value;
2635
- }))
2636
- ].sort();
2637
- var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
2638
- const appId = requireText(config.appId, "mobile.appId");
2639
- if (!APP_ID_PATTERN.test(appId)) {
2640
- throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
2641
- }
2642
- const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
2643
- const deepLinkScheme = config.deepLinks?.scheme?.trim().toLowerCase();
2644
- if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
2645
- throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
2646
3591
  }
2647
- return {
3592
+ if (!path) {
3593
+ const sourceCandidates = [
3594
+ join7(import.meta.dir, "remoteMacAgentEntry.ts"),
3595
+ join7(import.meta.dir, "..", "..", "src", "mobile", "remoteMacAgentEntry.ts")
3596
+ ];
3597
+ const source = await sourceCandidates.reduce(async (found, candidate) => await found ?? (await Bun.file(candidate).exists() ? candidate : undefined), Promise.resolve(undefined));
3598
+ if (!source)
3599
+ throw new Error("The AbsoluteJS installation does not contain its remote Mac agent artifact.");
3600
+ const outdir = join7(resolvePath2(projectRoot), ".absolutejs", "mobile", "remote-agent");
3601
+ await mkdir6(outdir, { recursive: true });
3602
+ const result = await Bun.build({
3603
+ entrypoints: [source],
3604
+ minify: true,
3605
+ outdir,
3606
+ target: "bun"
3607
+ });
3608
+ if (!result.success)
3609
+ throw new AggregateError(result.logs, "Failed to build the AbsoluteJS remote Mac agent.");
3610
+ path = join7(outdir, "remoteMacAgentEntry.js");
3611
+ }
3612
+ const bytes = await Bun.file(path).arrayBuffer();
3613
+ const sha256 = createHash7("sha256").update(new Uint8Array(bytes)).digest("hex");
3614
+ return { bytes: bytes.byteLength, path, sha256 };
3615
+ };
3616
+ var portableRelativePath = (root, path) => relative6(root, path).split(sep5).join(posix.sep);
3617
+ var portableMobileConfig = (project) => ({
3618
+ appId: project.config.appId,
3619
+ appName: project.config.appName,
3620
+ bundleDirectory: portableRelativePath(project.projectRoot, project.config.bundleDirectory),
3621
+ ...project.config.deepLinkScheme || project.config.deepLinkHosts.length > 1 || project.config.appleAppIdPrefix ? {
3622
+ deepLinks: {
3623
+ ...project.config.deepLinkScheme ? { scheme: project.config.deepLinkScheme } : {},
3624
+ hosts: project.config.deepLinkHosts,
3625
+ ...project.config.appleAppIdPrefix ? {
3626
+ apple: {
3627
+ appIdPrefix: project.config.appleAppIdPrefix
3628
+ }
3629
+ } : {}
3630
+ }
3631
+ } : {},
3632
+ entry: project.config.entry,
3633
+ ...project.config.iosVersion ? { ios: { version: project.config.iosVersion } } : {},
3634
+ nativeProject: {
3635
+ directory: portableRelativePath(project.projectRoot, project.config.nativeProjectDirectory),
3636
+ mode: "source"
3637
+ },
3638
+ platforms: ["ios"],
3639
+ server: { productionOrigin: project.config.productionOrigin }
3640
+ });
3641
+ var absoluteRemoteProjectSyncCommands = (project) => {
3642
+ const current = project.remoteProjectRoot;
3643
+ const parent = posix.dirname(current);
3644
+ const staging = posix.join(parent, `.incoming-${randomUUID3()}`);
3645
+ const previous = posix.join(parent, ".previous");
3646
+ const script = [
3647
+ "set -eu",
3648
+ `mkdir -p ${shellQuote(staging)}`,
3649
+ `tar -xf - -C ${shellQuote(staging)}`,
3650
+ `if [ -d ${shellQuote(posix.join(current, "node_modules"))} ]; then mv ${shellQuote(posix.join(current, "node_modules"))} ${shellQuote(posix.join(staging, "node_modules"))}; fi`,
3651
+ `if [ -d ${shellQuote(posix.join(current, ".absolutejs"))} ]; then mv ${shellQuote(posix.join(current, ".absolutejs"))} ${shellQuote(posix.join(staging, ".absolutejs"))}; fi`,
3652
+ `rm -rf ${shellQuote(previous)}`,
3653
+ `if [ -d ${shellQuote(current)} ]; then mv ${shellQuote(current)} ${shellQuote(previous)}; fi`,
3654
+ `mv ${shellQuote(staging)} ${shellQuote(current)}`,
3655
+ `rm -rf ${shellQuote(previous)}`
3656
+ ].join("; ");
3657
+ return {
3658
+ remote: [
3659
+ ...absoluteRemoteMacSshBase(project.profile),
3660
+ "/bin/sh -lc",
3661
+ shellQuote(script)
3662
+ ],
3663
+ tar: [
3664
+ "tar",
3665
+ "--exclude=.git",
3666
+ "--exclude=node_modules",
3667
+ "--exclude=build",
3668
+ "--exclude=.absolutejs",
3669
+ "-cf",
3670
+ "-",
3671
+ "-C",
3672
+ project.projectRoot,
3673
+ "."
3674
+ ]
3675
+ };
3676
+ };
3677
+ var syncAbsoluteRemoteMacProject = async (project) => {
3678
+ const commands = absoluteRemoteProjectSyncCommands(project);
3679
+ const archive = Bun.spawn(commands.tar, {
3680
+ stderr: "pipe",
3681
+ stdout: "pipe"
3682
+ });
3683
+ const upload = Bun.spawn(commands.remote, {
3684
+ stderr: "pipe",
3685
+ stdin: archive.stdout,
3686
+ stdout: "pipe"
3687
+ });
3688
+ const [archiveExit, uploadExit, archiveError, uploadError] = await Promise.all([
3689
+ archive.exited,
3690
+ upload.exited,
3691
+ new Response(archive.stderr).text(),
3692
+ new Response(upload.stderr).text()
3693
+ ]);
3694
+ if (archiveExit !== 0 || uploadExit !== 0)
3695
+ throw new Error(`Remote Mac project synchronization failed: ${(archiveError || uploadError).trim()}`);
3696
+ const install = await defaultTransport.capture([
3697
+ ...absoluteRemoteMacSshBase(project.profile),
3698
+ "/bin/sh -lc",
3699
+ shellQuote(`cd ${shellQuote(project.remoteProjectRoot)} && ${shellQuote(project.profile.bunPath)} install --frozen-lockfile`)
3700
+ ]);
3701
+ requireRemoteSuccess(install, "Remote Mac dependency installation");
3702
+ };
3703
+ var consumeLines2 = async (stream, onLine) => {
3704
+ const reader = stream.getReader();
3705
+ const decoder = new TextDecoder;
3706
+ let buffered = "";
3707
+ try {
3708
+ while (true) {
3709
+ const { done, value } = await reader.read();
3710
+ if (done)
3711
+ break;
3712
+ buffered += decoder.decode(value, { stream: true });
3713
+ const lines = buffered.split(/\r?\n/u);
3714
+ buffered = lines.pop() ?? "";
3715
+ lines.forEach(onLine);
3716
+ }
3717
+ buffered += decoder.decode();
3718
+ if (buffered)
3719
+ onLine(buffered);
3720
+ } finally {
3721
+ reader.releaseLock();
3722
+ }
3723
+ };
3724
+ var startAbsoluteRemoteIosDevSession = async (options) => {
3725
+ const startedAt = performance.now();
3726
+ const transport = options.transport ?? defaultTransport;
3727
+ const installAgent = options.installAgent ?? installAbsoluteRemoteMacAgent;
3728
+ const syncProject = options.syncProject ?? syncAbsoluteRemoteMacProject;
3729
+ const agentStartedAt = performance.now();
3730
+ const agent = await installAgent(options.project);
3731
+ const agentDuration = performance.now() - agentStartedAt;
3732
+ const syncStartedAt = performance.now();
3733
+ await syncProject(options.project);
3734
+ const syncDuration = performance.now() - syncStartedAt;
3735
+ const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
3736
+ const remoteCommand = [
3737
+ `cd ${shellQuote(options.project.remoteProjectRoot)}`,
3738
+ "&&",
3739
+ "exec",
3740
+ shellQuote(options.project.profile.bunPath),
3741
+ shellQuote(agent.remotePath),
3742
+ "--port",
3743
+ String(options.port),
3744
+ "--mobile-config",
3745
+ shellQuote(encodedConfig),
3746
+ ...options.https ? ["--https"] : []
3747
+ ].join(" ");
3748
+ const command = [
3749
+ ...absoluteRemoteMacSshBase(options.project.profile),
3750
+ "-o",
3751
+ "ExitOnForwardFailure=yes",
3752
+ "-R",
3753
+ `${options.port}:127.0.0.1:${options.port}`,
3754
+ "/bin/sh -lc",
3755
+ shellQuote(remoteCommand)
3756
+ ];
3757
+ const connectStartedAt = performance.now();
3758
+ const process2 = transport.spawn(command, { signal: options.signal });
3759
+ let state = "syncing";
3760
+ let ready;
3761
+ let fatal;
3762
+ const pending = new Map;
3763
+ let resolveReady;
3764
+ let rejectReady;
3765
+ const readyPromise = new Promise((resolve6, reject) => {
3766
+ resolveReady = resolve6;
3767
+ rejectReady = reject;
3768
+ });
3769
+ const handleEvent = (event) => {
3770
+ if (event.v !== ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION) {
3771
+ rejectReady(new Error("Remote Mac protocol version mismatch."));
3772
+ return;
3773
+ }
3774
+ if (event.type === "log")
3775
+ options.log?.(event.message);
3776
+ if (event.type === "native-log")
3777
+ options.nativeLog?.(event.entry);
3778
+ if (event.type === "state") {
3779
+ ({ state } = event);
3780
+ options.onStateChange?.(state);
3781
+ }
3782
+ if (event.type === "timing")
3783
+ options.onPhaseTiming?.(event);
3784
+ if (event.type === "ready") {
3785
+ ready = event;
3786
+ resolveReady();
3787
+ }
3788
+ if (event.type === "fatal") {
3789
+ fatal = new Error(event.error);
3790
+ rejectReady(fatal);
3791
+ }
3792
+ if (event.type === "response") {
3793
+ const request2 = pending.get(event.id);
3794
+ if (!request2)
3795
+ return;
3796
+ pending.delete(event.id);
3797
+ if (event.ok)
3798
+ request2.resolve(event.result);
3799
+ else
3800
+ request2.reject(new Error(event.error ?? "Remote command failed."));
3801
+ }
3802
+ };
3803
+ const stdoutDone = consumeLines2(process2.stdout, (line) => {
3804
+ if (!line.startsWith(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX))
3805
+ return;
3806
+ try {
3807
+ handleEvent(JSON.parse(line.slice(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX.length)));
3808
+ } catch {
3809
+ options.log?.(`Remote Mac emitted an invalid protocol event.`);
3810
+ }
3811
+ }).catch((error) => {
3812
+ fatal = error instanceof Error ? error : new Error("Failed to read the remote Mac protocol stream.");
3813
+ rejectReady(fatal);
3814
+ });
3815
+ const stderrDone = consumeLines2(process2.stderr, (line) => options.log?.(`[remote] ${line}`)).catch((error) => options.log?.(`[remote] ${error instanceof Error ? error.message : "Failed to read SSH stderr."}`));
3816
+ process2.exited.then(async (exitCode) => {
3817
+ await Promise.all([stdoutDone, stderrDone]);
3818
+ const error = fatal ?? new Error(`Remote Mac connection closed with status ${exitCode}.`);
3819
+ if (!ready)
3820
+ rejectReady(error);
3821
+ pending.forEach(({ reject }) => reject(error));
3822
+ pending.clear();
3823
+ return;
3824
+ });
3825
+ await readyPromise;
3826
+ if (!ready)
3827
+ throw fatal ?? new Error("Remote Mac did not become ready.");
3828
+ const totalDuration = performance.now() - startedAt;
3829
+ let currentReady = {
3830
+ ...ready,
3831
+ timings: {
3832
+ ...ready.timings,
3833
+ "remote-agent": agentDuration,
3834
+ "remote-connect": performance.now() - connectStartedAt,
3835
+ "remote-sync": syncDuration,
3836
+ total: totalDuration
3837
+ }
3838
+ };
3839
+ options.log?.(`Remote Mac connected (${options.project.profile.name}); agent ${agent.uploaded ? "uploaded" : "cache hit"}, project synced, and iOS ready in ${totalDuration.toFixed(2)}ms.`);
3840
+ const request = (commandName) => {
3841
+ const id = randomUUID3();
3842
+ const response = new Promise((resolve6, reject) => pending.set(id, { reject, resolve: resolve6 }));
3843
+ process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
3844
+ `);
3845
+ const flush = async () => {
3846
+ for (let attempt = 0;attempt < REMOTE_STDIN_FLUSH_ATTEMPTS; attempt++) {
3847
+ try {
3848
+ await process2.stdin.flush();
3849
+ return;
3850
+ } catch {
3851
+ await Promise.resolve();
3852
+ }
3853
+ }
3854
+ };
3855
+ return flush().then(() => response);
3856
+ };
3857
+ let closed = false;
3858
+ const close = async () => {
3859
+ if (closed)
3860
+ return;
3861
+ closed = true;
3862
+ await request("close").catch(() => {
3863
+ return;
3864
+ });
3865
+ process2.stdin.end();
3866
+ await process2.exited.catch(() => {
3867
+ return;
3868
+ });
3869
+ };
3870
+ const makeSession = () => ({
3871
+ close,
3872
+ nativeCacheHit: currentReady.nativeCacheHit,
3873
+ startedSimulator: currentReady.startedSimulator,
3874
+ timings: currentReady.timings,
3875
+ udid: currentReady.udid,
3876
+ rebuild: async () => {
3877
+ const rebuildStartedAt = performance.now();
3878
+ const rebuildSyncStartedAt = performance.now();
3879
+ await syncProject(options.project);
3880
+ const rebuildSyncDuration = performance.now() - rebuildSyncStartedAt;
3881
+ const result = await request("rebuild");
3882
+ currentReady = {
3883
+ ...result,
3884
+ timings: {
3885
+ ...result.timings,
3886
+ "remote-sync": rebuildSyncDuration,
3887
+ total: performance.now() - rebuildStartedAt
3888
+ }
3889
+ };
3890
+ return makeSession();
3891
+ },
3892
+ relaunch: async () => {
3893
+ await request("relaunch");
3894
+ },
3895
+ screenshot: async (destination) => {
3896
+ const result = await request("screenshot");
3897
+ const target = resolvePath2(options.project.projectRoot, destination);
3898
+ const targetRelative = relative6(options.project.projectRoot, target);
3899
+ if (targetRelative.startsWith("..") || isAbsolute5(targetRelative))
3900
+ throw new Error("iOS screenshot must remain inside the project.");
3901
+ await mkdir6(dirname5(target), { recursive: true });
3902
+ await writeFile7(target, Buffer.from(result.data, "base64"));
3903
+ return target;
3904
+ },
3905
+ get state() {
3906
+ return state;
3907
+ }
3908
+ });
3909
+ return makeSession();
3910
+ };
3911
+ // src/mobile/associationFiles.ts
3912
+ import {
3913
+ access as access7,
3914
+ mkdir as mkdir7,
3915
+ readFile as readFile9,
3916
+ rename as rename8,
3917
+ rm as rm6,
3918
+ writeFile as writeFile8
3919
+ } from "fs/promises";
3920
+ import { resolve as resolve7 } from "path";
3921
+ import { Elysia } from "elysia";
3922
+
3923
+ // src/mobile/config.ts
3924
+ import { resolve as resolve6 } from "path";
3925
+ var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
3926
+ var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
3927
+ var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
3928
+ var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
3929
+ var HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
3930
+ var resolveProjectPath = (projectRoot, value, field2) => {
3931
+ const root = resolve6(projectRoot);
3932
+ const path = resolve6(root, value);
3933
+ if (path !== root && !path.startsWith(`${root}/`)) {
3934
+ throw new TypeError(`${field2} must remain inside the project root.`);
3935
+ }
3936
+ return path;
3937
+ };
3938
+ var requireText = (value, field2) => {
3939
+ const normalized = value.trim();
3940
+ if (!normalized)
3941
+ throw new TypeError(`${field2} must not be empty.`);
3942
+ return normalized;
3943
+ };
3944
+ var normalizeEntry = (entry) => {
3945
+ const normalized = requireText(entry ?? "/", "mobile.entry");
3946
+ if (!normalized.startsWith("/") || normalized.startsWith("//")) {
3947
+ throw new TypeError("mobile.entry must be an absolute application path.");
3948
+ }
3949
+ return normalized;
3950
+ };
3951
+ var normalizeProductionOrigin = (value) => {
3952
+ const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
3953
+ const isLoopbackHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
3954
+ if (parsed.protocol !== "https:" && !isLoopbackHttp) {
3955
+ throw new TypeError("mobile.server.productionOrigin must use HTTPS, except for a loopback development origin.");
3956
+ }
3957
+ if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
3958
+ throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
3959
+ }
3960
+ return parsed.origin;
3961
+ };
3962
+ var normalizePlatforms = (platforms) => {
3963
+ const normalized = [
3964
+ ...new Set(platforms ?? ["ios", "android"])
3965
+ ];
3966
+ if (normalized.length === 0) {
3967
+ throw new TypeError("mobile.platforms must contain at least one platform.");
3968
+ }
3969
+ return normalized;
3970
+ };
3971
+ var normalizeHosts = (hosts, productionOrigin) => {
3972
+ const normalizeHostname = (host) => {
3973
+ const value = requireText(host, "mobile.deepLinks.hosts").toLowerCase();
3974
+ if (!HOSTNAME_PATTERN.test(value)) {
3975
+ throw new TypeError("mobile.deepLinks.hosts entries must be valid hostnames without ports, wildcards, or paths.");
3976
+ }
3977
+ return value;
3978
+ };
3979
+ const productionHostname = new URL(productionOrigin).hostname;
3980
+ const normalized = new Set(productionHostname === "[::1]" ? [] : [normalizeHostname(productionHostname)]);
3981
+ for (const host of hosts ?? []) {
3982
+ normalized.add(normalizeHostname(host));
3983
+ }
3984
+ return [...normalized].sort();
3985
+ };
3986
+ var normalizeAppleAppIdPrefix = (value) => {
3987
+ if (value === undefined)
3988
+ return;
3989
+ const normalized = requireText(value, "mobile.deepLinks.apple.appIdPrefix").toUpperCase();
3990
+ if (!APPLE_APP_ID_PREFIX_PATTERN.test(normalized)) {
3991
+ throw new TypeError("mobile.deepLinks.apple.appIdPrefix must contain ten letters or digits.");
3992
+ }
3993
+ return normalized;
3994
+ };
3995
+ var normalizeIosVersion = (value) => {
3996
+ if (value === undefined)
3997
+ return;
3998
+ const normalized = requireText(value, "mobile.ios.version");
3999
+ if (!/^\d+(?:\.\d+){0,2}$/u.test(normalized)) {
4000
+ throw new TypeError("mobile.ios.version must contain one to three dot-separated integer components, for example 1.4.0.");
4001
+ }
4002
+ return normalized;
4003
+ };
4004
+ var normalizeCertificateFingerprints = (values) => [
4005
+ ...new Set((values ?? []).map((value) => requireText(value, "mobile.deepLinks.android.sha256CertificateFingerprints").replaceAll(":", "").toUpperCase()).map((value) => {
4006
+ if (!CERTIFICATE_FINGERPRINT_PATTERN.test(value)) {
4007
+ throw new TypeError("mobile.deepLinks.android.sha256CertificateFingerprints entries must be SHA-256 certificate fingerprints.");
4008
+ }
4009
+ return value.match(/.{2}/g)?.join(":") ?? value;
4010
+ }))
4011
+ ].sort();
4012
+ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
4013
+ const appId = requireText(config.appId, "mobile.appId");
4014
+ if (!APP_ID_PATTERN.test(appId)) {
4015
+ throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
4016
+ }
4017
+ const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
4018
+ const deepLinkScheme = (config.deepLinks?.scheme ?? appId).trim().toLowerCase();
4019
+ if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
4020
+ throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
4021
+ }
4022
+ return {
2648
4023
  androidCertificateFingerprints: normalizeCertificateFingerprints(config.deepLinks?.android?.sha256CertificateFingerprints),
2649
4024
  appId,
2650
4025
  appleAppIdPrefix: normalizeAppleAppIdPrefix(config.deepLinks?.apple?.appIdPrefix),
@@ -2657,7 +4032,8 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
2657
4032
  iosVersion: normalizeIosVersion(config.ios?.version),
2658
4033
  nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
2659
4034
  platforms: normalizePlatforms(config.platforms),
2660
- productionOrigin
4035
+ productionOrigin,
4036
+ pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile")
2661
4037
  };
2662
4038
  };
2663
4039
 
@@ -2671,7 +4047,7 @@ var HTTP_OK = 200;
2671
4047
  var VERIFY_TIMEOUT_MS = 1e4;
2672
4048
  var ANDROID_ASSOCIATION_PATH = "/.well-known/assetlinks.json";
2673
4049
  var APPLE_ASSOCIATION_PATH = "/.well-known/apple-app-site-association";
2674
- var missingIdentity = (field, platform) => new TypeError(`${field} is required to publish ${platform} deep-link association files.`);
4050
+ var missingIdentity = (field2, platform) => new TypeError(`${field2} is required to publish ${platform} deep-link association files.`);
2675
4051
  var createAppleDocument = (config, requireAll) => {
2676
4052
  if (!config.platforms.includes("ios"))
2677
4053
  return;
@@ -2742,7 +4118,7 @@ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {})
2742
4118
  var writeAtomic = async (path, source) => {
2743
4119
  let current;
2744
4120
  try {
2745
- current = await readFile8(path, "utf8");
4121
+ current = await readFile9(path, "utf8");
2746
4122
  } catch (error) {
2747
4123
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2748
4124
  throw error;
@@ -2751,8 +4127,8 @@ var writeAtomic = async (path, source) => {
2751
4127
  if (current === source)
2752
4128
  return false;
2753
4129
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2754
- await writeFile7(temporary, source, { flag: "wx" });
2755
- await rename7(temporary, path);
4130
+ await writeFile8(temporary, source, { flag: "wx" });
4131
+ await rename8(temporary, path);
2756
4132
  return true;
2757
4133
  };
2758
4134
  var exists2 = async (path) => {
@@ -2767,7 +4143,7 @@ var assertOwnedOutput = async (root) => {
2767
4143
  const path = resolve7(root, OWNERSHIP_FILE);
2768
4144
  let ownership;
2769
4145
  try {
2770
- ownership = JSON.parse(await readFile8(path, "utf8"));
4146
+ ownership = JSON.parse(await readFile9(path, "utf8"));
2771
4147
  } catch {
2772
4148
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
2773
4149
  }
@@ -2781,12 +4157,12 @@ var publishGeneratedDirectory = async (temporary, root) => {
2781
4157
  await assertOwnedOutput(root);
2782
4158
  const backup = `${root}.${crypto.randomUUID()}.previous`;
2783
4159
  if (hasCurrent)
2784
- await rename7(root, backup);
4160
+ await rename8(root, backup);
2785
4161
  try {
2786
- await rename7(temporary, root);
4162
+ await rename8(temporary, root);
2787
4163
  } catch (error) {
2788
4164
  if (hasCurrent)
2789
- await rename7(backup, root);
4165
+ await rename8(backup, root);
2790
4166
  throw error;
2791
4167
  }
2792
4168
  if (hasCurrent)
@@ -2794,7 +4170,7 @@ var publishGeneratedDirectory = async (temporary, root) => {
2794
4170
  };
2795
4171
  var materializeHost = async (root, host, files) => {
2796
4172
  const directory = resolve7(root, host, ".well-known");
2797
- await mkdir6(directory, { recursive: true });
4173
+ await mkdir7(directory, { recursive: true });
2798
4174
  return Promise.all(files.map(async ([name, document]) => {
2799
4175
  const path = resolve7(directory, name);
2800
4176
  await writeAtomic(path, `${JSON.stringify(document, null, 2)}
@@ -2832,7 +4208,7 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
2832
4208
  if (documents.apple) {
2833
4209
  files.push(["apple-app-site-association", documents.apple]);
2834
4210
  }
2835
- await mkdir6(temporary, { recursive: true });
4211
+ await mkdir7(temporary, { recursive: true });
2836
4212
  try {
2837
4213
  const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
2838
4214
  await writeAtomic(resolve7(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
@@ -2903,15 +4279,23 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
2903
4279
  };
2904
4280
  };
2905
4281
  // src/mobile/buildPipeline.ts
2906
- import { readFile as readFile12 } from "fs/promises";
2907
- import { join as join10, resolve as resolve10 } from "path";
4282
+ import { readFile as readFile13 } from "fs/promises";
4283
+ import { join as join14, resolve as resolve12 } from "path";
2908
4284
  import { pathToFileURL as pathToFileURL2 } from "url";
2909
4285
 
2910
4286
  // src/mobile/buildRelease.ts
2911
- import { createHash as createHash7 } from "crypto";
2912
- import { readFile as readFile9 } from "fs/promises";
2913
- import { join as join7, relative as relative6, resolve as resolve8 } from "path";
2914
- var sha256 = (bytes) => createHash7("sha256").update(bytes).digest("hex");
4287
+ import { createHash as createHash8 } from "crypto";
4288
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
4289
+ import { basename as basename2, dirname as dirname6, extname, join as join8, relative as relative7, resolve as resolve8 } from "path";
4290
+ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
4291
+ var STATIC_SCRIPT_PATTERN = /(<script\b[^>]*?\bsrc\s*=\s*["'])(\/[^"']+\.(?:js|ts))(["'][^>]*>)/giu;
4292
+ var rewriteStaticScriptPaths = (source, manifest) => source.replace(STATIC_SCRIPT_PATTERN, (match, prefix, path, suffix) => {
4293
+ if (path.endsWith("/htmx.min.js"))
4294
+ return match;
4295
+ const key = toPascal(basename2(path, extname(path)));
4296
+ const builtPath = manifest[key];
4297
+ return builtPath ? `${prefix}${builtPath}${suffix}` : match;
4298
+ });
2915
4299
  var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
2916
4300
  var resolveAssetPath = (buildDirectory, assetPath) => {
2917
4301
  const resolvedBuildDirectory = resolve8(buildDirectory);
@@ -2919,29 +4303,51 @@ var resolveAssetPath = (buildDirectory, assetPath) => {
2919
4303
  if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
2920
4304
  return resolvedAsset;
2921
4305
  }
2922
- return join7(buildDirectory, assetPath.replace(/^\/+/, ""));
4306
+ return join8(buildDirectory, assetPath.replace(/^\/+/, ""));
2923
4307
  };
2924
4308
  var pageFor = async (metadata, manifest, buildDirectory) => {
2925
4309
  const assetPath = manifest[metadata.bundleKey];
2926
4310
  if (!assetPath) {
2927
4311
  throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
2928
4312
  }
2929
- const resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
2930
- const bytes = await readFile9(resolvedAssetPath);
2931
- const bundlePath = `/${relative6(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
4313
+ let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
4314
+ if (metadata.framework === "html" || metadata.framework === "htmx") {
4315
+ const source = await readFile10(resolvedAssetPath, "utf8");
4316
+ const rewritten = rewriteStaticScriptPaths(source, manifest);
4317
+ const documentHash = sha256(new TextEncoder().encode(rewritten));
4318
+ resolvedAssetPath = join8(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
4319
+ await mkdir8(dirname6(resolvedAssetPath), { recursive: true });
4320
+ await writeFile9(resolvedAssetPath, rewritten);
4321
+ }
4322
+ const pageAssetKey = metadata.bundleKey.replace(/Index$/u, "");
4323
+ const styleAssetPath = [
4324
+ `${pageAssetKey}BundledCSS`,
4325
+ `${pageAssetKey}CompiledCSS`
4326
+ ].map((key) => manifest[key]).find((path) => typeof path === "string");
4327
+ const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
4328
+ const [bytes, styleBytes] = await Promise.all([
4329
+ readFile10(resolvedAssetPath),
4330
+ resolvedStylePath ? readFile10(resolvedStylePath) : undefined
4331
+ ]);
4332
+ const bundlePath = `/${relative7(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
4333
+ const styleBundlePath = resolvedStylePath ? `/${relative7(resolve8(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
2932
4334
  return {
2933
4335
  bundleHash: sha256(bytes),
2934
4336
  bundlePath,
2935
4337
  contract: metadata.contract,
2936
4338
  framework: metadata.framework,
2937
4339
  pageId: metadata.pageId,
2938
- propsSchemaHash: metadata.propsSchemaHash
4340
+ propsSchemaHash: metadata.propsSchemaHash,
4341
+ ...styleBytes && styleBundlePath ? {
4342
+ styleBundleHash: sha256(styleBytes),
4343
+ styleBundlePath
4344
+ } : {}
2939
4345
  };
2940
4346
  };
2941
4347
  var buildAbsoluteMobileCompatibilityRelease = async (options) => {
2942
4348
  const [captured, producerBytes] = await Promise.all([
2943
4349
  captureAbsoluteMobileRouteGraph(options.app),
2944
- readFile9(options.producerPath)
4350
+ readFile10(options.producerPath)
2945
4351
  ]);
2946
4352
  if (captured.length === 0) {
2947
4353
  throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
@@ -2957,11 +4363,19 @@ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
2957
4363
  const pages = await Promise.all([...metadataByPage.values()].map((metadata) => pageFor(metadata, options.manifest, options.buildDirectory)));
2958
4364
  const producerHash = sha256(producerBytes);
2959
4365
  const appBuild = `ambuild_${sha256(new TextEncoder().encode(JSON.stringify({
2960
- pages: pages.map(({ bundleHash, bundlePath, contract, pageId }) => ({
4366
+ pages: pages.map(({
4367
+ bundleHash,
4368
+ bundlePath,
4369
+ contract,
4370
+ pageId,
4371
+ styleBundleHash,
4372
+ styleBundlePath
4373
+ }) => ({
2961
4374
  bundleHash,
2962
4375
  bundlePath,
2963
4376
  contract,
2964
- pageId
4377
+ pageId,
4378
+ ...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
2965
4379
  })),
2966
4380
  producerHash,
2967
4381
  runtime: options.runtime
@@ -2970,8 +4384,8 @@ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
2970
4384
  if (priorAppIds.size > 1 || priorAppIds.size === 1 && !priorAppIds.has(options.appId)) {
2971
4385
  throw new TypeError("Previous mobile compatibility artifacts belong to another app.");
2972
4386
  }
2973
- const unchanged = options.previousArtifacts?.find((artifact2) => artifact2.appBuild === appBuild && artifact2.runtime === options.runtime);
2974
- const generation = unchanged?.generation ?? Math.max(0, ...(options.previousArtifacts ?? []).map((artifact2) => artifact2.generation)) + 1;
4387
+ const unchanged2 = options.previousArtifacts?.find((artifact2) => artifact2.appBuild === appBuild && artifact2.runtime === options.runtime);
4388
+ const generation = unchanged2?.generation ?? Math.max(0, ...(options.previousArtifacts ?? []).map((artifact2) => artifact2.generation)) + 1;
2975
4389
  const artifact = createAbsoluteMobileCompatibilityArtifact({
2976
4390
  appBuild,
2977
4391
  appId: options.appId,
@@ -3021,16 +4435,17 @@ var captureAbsoluteMobileRouteGraph = async (app) => {
3021
4435
 
3022
4436
  // src/mobile/capacitorBundle.ts
3023
4437
  import {
4438
+ cp,
3024
4439
  copyFile as copyFile5,
3025
- mkdir as mkdir7,
4440
+ mkdir as mkdir9,
3026
4441
  mkdtemp as mkdtemp4,
3027
- readFile as readFile10,
3028
- rename as rename8,
4442
+ readFile as readFile11,
4443
+ rename as rename9,
3029
4444
  rm as rm7,
3030
- writeFile as writeFile8
4445
+ writeFile as writeFile10
3031
4446
  } from "fs/promises";
3032
4447
  import { existsSync as existsSync2 } from "fs";
3033
- import { basename as basename2, dirname as dirname5, extname, join as join8, resolve as resolve9 } from "path";
4448
+ import { basename as basename3, dirname as dirname7, extname as extname2, join as join9, relative as relative8, resolve as resolve9 } from "path";
3034
4449
 
3035
4450
  // src/mobile/routeMatcher.ts
3036
4451
  var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
@@ -3303,6 +4718,13 @@ class AbsoluteMobilePageProtocolError extends Error {
3303
4718
  this.code = code;
3304
4719
  }
3305
4720
  }
4721
+ var disposeAbsoluteMobilePage = async (target = window) => {
4722
+ const dispose = target.__ABSOLUTE_PAGE_DISPOSE__;
4723
+ target.__ABSOLUTE_PAGE_DISPOSE__ = undefined;
4724
+ target.__ABSOLUTE_PAGE_READY__ = undefined;
4725
+ if (dispose)
4726
+ await dispose();
4727
+ };
3306
4728
  var frameworks4 = new Set([
3307
4729
  "angular",
3308
4730
  "ember",
@@ -3358,12 +4780,17 @@ var activateAbsoluteMobilePage = async (value, options) => {
3358
4780
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "Expected a renderable mobile page response.");
3359
4781
  }
3360
4782
  const target = options.target ?? window;
4783
+ await disposeAbsoluteMobilePage(target);
3361
4784
  target.__INITIAL_PROPS__ = envelope.response.props;
4785
+ target.__ABS_ANGULAR_REQUEST_CONTEXT__ = envelope.response.props;
3362
4786
  target.__ABSOLUTE_PAGE_RENDER_MODE__ = "client";
3363
4787
  await options.loadPage({
3364
4788
  contract: envelope.response.contract,
3365
4789
  pageId: envelope.response.pageId
3366
4790
  });
4791
+ if (target.__ABSOLUTE_PAGE_READY__) {
4792
+ await target.__ABSOLUTE_PAGE_READY__;
4793
+ }
3367
4794
  return {
3368
4795
  contract: envelope.response.contract,
3369
4796
  kind: "rendered",
@@ -3456,19 +4883,57 @@ var resolveAbsoluteMobileDeepLink = (manifest, value) => {
3456
4883
  }
3457
4884
  return `${url.pathname || "/"}${url.search}${url.hash}`;
3458
4885
  };
4886
+ var resolveAbsoluteMobileNavigation = (manifest, value, localOrigin) => {
4887
+ const url = new URL(value, `${localOrigin}/`);
4888
+ const local = new URL(localOrigin);
4889
+ const production = new URL(manifest.productionOrigin);
4890
+ const matches = (candidate, allowed) => candidate.protocol === allowed.protocol && candidate.host === allowed.host;
4891
+ if (!matches(url, local) && !matches(url, production)) {
4892
+ return;
4893
+ }
4894
+ return `${url.pathname}${url.search}${url.hash}`;
4895
+ };
3459
4896
 
3460
4897
  // src/mobile/capacitorBundle.ts
3461
4898
  var MANIFEST_FILE = "absolute-mobile-manifest.json";
3462
4899
  var BOOTSTRAP_FILE = "absolute-mobile-bootstrap.js";
3463
4900
  var INDEX_FILE = "index.html";
3464
- var CLIENT_IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)["'](\/[^"']+)["']/gu;
4901
+ var CLIENT_CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?|url\(\s*)["']?((?:\/|\.\.\/|\.\/)[^"')\s]+)["']?\s*\)?/gu;
4902
+ var CLIENT_MARKUP_DEPENDENCY_PATTERN = /<(?:script\b[^>]*\bsrc|link\b[^>]*\bhref|img\b[^>]*\bsrc|source\b[^>]*\bsrcset)\s*=\s*["']((?:\/|\.\.\/|\.\/)[^"',\s]+)/giu;
4903
+ var CAPACITOR_CLIENT_FRAMEWORKS = new Set([
4904
+ "angular",
4905
+ "html",
4906
+ "htmx",
4907
+ "react",
4908
+ "svelte",
4909
+ "vue"
4910
+ ]);
4911
+ var CLIENT_ASSET_DIRECTORIES = ["assets", "html", "htmx", "indexes"];
3465
4912
  var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
3466
4913
  var shellBootstrapModule = () => {
3467
- const candidate = ["js", "ts"].map((extension) => join8(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
4914
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
3468
4915
  if (candidate)
3469
4916
  return candidate;
3470
4917
  throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
3471
4918
  };
4919
+ var shellAuthModule = () => {
4920
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellAuth.${extension}`)).find(existsSync2);
4921
+ if (candidate)
4922
+ return candidate;
4923
+ throw new TypeError("AbsoluteJS mobile auth shell module is missing.");
4924
+ };
4925
+ var shellSyncModule = () => {
4926
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellSync.${extension}`)).find(existsSync2);
4927
+ if (candidate)
4928
+ return candidate;
4929
+ throw new TypeError("AbsoluteJS mobile Sync shell module is missing.");
4930
+ };
4931
+ var shellPushModule = () => {
4932
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellPush.${extension}`)).find(existsSync2);
4933
+ if (candidate)
4934
+ return candidate;
4935
+ throw new TypeError("AbsoluteJS mobile push shell module is missing.");
4936
+ };
3472
4937
  var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3473
4938
  var indexHtml = (appName) => `<!doctype html>
3474
4939
  <html>
@@ -3492,11 +4957,58 @@ var sourceAssetPath = (buildDirectory, bundlePath) => {
3492
4957
  }
3493
4958
  return asset;
3494
4959
  };
3495
- var buildShellBootstrap = async (staging) => {
4960
+ var importEntryTarget = (entry) => {
4961
+ if (typeof entry === "string")
4962
+ return entry;
4963
+ if (typeof entry === "object" && entry !== null)
4964
+ return Reflect.get(entry, "import");
4965
+ return;
4966
+ };
4967
+ var resolveProjectImport = async (projectRoot, specifier) => {
4968
+ const segments = specifier.split("/");
4969
+ const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
4970
+ const subpath = specifier.slice(packageName.length);
4971
+ const packageDirectory = join9(resolve9(projectRoot), "node_modules", packageName);
4972
+ const manifest = JSON.parse(await readFile11(join9(packageDirectory, "package.json"), "utf8"));
4973
+ const exports = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "exports") : undefined;
4974
+ const entry = typeof exports === "object" && exports !== null ? Reflect.get(exports, subpath ? `.${subpath}` : ".") : undefined;
4975
+ const target = importEntryTarget(entry);
4976
+ if (typeof target !== "string" || !target.startsWith("./"))
4977
+ throw new TypeError(`${specifier} does not publish an import entry.`);
4978
+ const resolved = resolve9(packageDirectory, target);
4979
+ if (!resolved.startsWith(`${resolve9(packageDirectory)}/`))
4980
+ throw new TypeError(`${specifier} has an unsafe import entry.`);
4981
+ return resolved;
4982
+ };
4983
+ var buildShellBootstrap = async (staging, auth, sync, storagePrefix, deviceCapabilities, projectRoot) => {
3496
4984
  const modulePath = shellBootstrapModule();
3497
- const entryPath = join8(staging, ".absolute-mobile-entry.ts");
3498
- await writeFile8(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
3499
- void startAbsoluteMobileShell();
4985
+ const authImport = auth ? `import { createAbsoluteMobileShellAuth } from ${JSON.stringify(shellAuthModule())};
4986
+ ` : "";
4987
+ const options = auth ? `{ createAuth: createAbsoluteMobileShellAuth${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : "";
4988
+ const syncImport = sync ? `import { installAbsoluteMobileShellSync } from ${JSON.stringify(shellSyncModule())};
4989
+ ` : "";
4990
+ const pushIndex = deviceCapabilities.capabilities.indexOf("pushNotifications");
4991
+ const push = pushIndex !== -1;
4992
+ const pushImport = push ? `import { createAbsoluteMobileShellPush } from ${JSON.stringify(shellPushModule())};
4993
+ ` : "";
4994
+ const capabilityImports = (await Promise.all(deviceCapabilities.capabilities.map(async (name, index) => {
4995
+ const provider = deviceCapabilities.providers[name];
4996
+ if (!provider)
4997
+ throw new TypeError(`Missing device capability provider ${name}.`);
4998
+ return `import { ${provider.factory} as absoluteDeviceCapability${index} } from ${JSON.stringify(await resolveProjectImport(projectRoot, provider.module))};`;
4999
+ }))).join(`
5000
+ `);
5001
+ const pushSetup = push ? `const absoluteMobilePush = createAbsoluteMobileShellPush();
5002
+ const absoluteMobilePushCapability = absoluteDeviceCapability${pushIndex}(absoluteMobilePush.capabilityOptions);
5003
+ ` : "";
5004
+ const capabilityOptions = deviceCapabilities.capabilities.map((name, index) => `${JSON.stringify(name)}: ${name === "pushNotifications" ? "absoluteMobilePushCapability" : `absoluteDeviceCapability${index}()`}`).join(", ");
5005
+ const entryPath = join9(staging, ".absolute-mobile-entry.ts");
5006
+ const baseAdapterModule = await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor");
5007
+ await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
5008
+ import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};
5009
+ ${authImport}${syncImport}${pushImport}${capabilityImports}
5010
+ ${pushSetup}installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });
5011
+ void startAbsoluteMobileShell(${push ? `{ createAuth: (config, options) => createAbsoluteMobileShellAuth(config, options), beforeSignOut: absoluteMobilePush.beforeSignOut, connectPush: (auth) => absoluteMobilePush.connect(auth, absoluteMobilePushCapability)${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : options});
3500
5012
  `);
3501
5013
  const build = await Bun.build({
3502
5014
  entrypoints: [entryPath],
@@ -3507,7 +5019,7 @@ void startAbsoluteMobileShell();
3507
5019
  if (!build.success || build.outputs.length !== 1) {
3508
5020
  throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
3509
5021
  }
3510
- await rename8(build.outputs[0]?.path ?? "", join8(staging, BOOTSTRAP_FILE));
5022
+ await rename9(build.outputs[0]?.path ?? "", join9(staging, BOOTSTRAP_FILE));
3511
5023
  await rm7(entryPath, { force: true });
3512
5024
  };
3513
5025
  var removePreviousBundle = async (backup, moved) => {
@@ -3518,20 +5030,20 @@ var removePreviousBundle = async (backup, moved) => {
3518
5030
  var restorePreviousBundle = async (backup, destination, moved) => {
3519
5031
  if (!moved)
3520
5032
  return;
3521
- await rename8(backup, destination);
5033
+ await rename9(backup, destination);
3522
5034
  };
3523
5035
  var installBundle = async (staging, destination) => {
3524
5036
  const backup = `${destination}.previous-${crypto.randomUUID()}`;
3525
5037
  let movedPrevious = false;
3526
5038
  try {
3527
- await rename8(destination, backup);
5039
+ await rename9(destination, backup);
3528
5040
  movedPrevious = true;
3529
5041
  } catch (error) {
3530
5042
  if (!errorHasCode2(error, "ENOENT"))
3531
5043
  throw error;
3532
5044
  }
3533
5045
  try {
3534
- await rename8(staging, destination);
5046
+ await rename9(staging, destination);
3535
5047
  await removePreviousBundle(backup, movedPrevious);
3536
5048
  } catch (error) {
3537
5049
  await restorePreviousBundle(backup, destination, movedPrevious);
@@ -3539,21 +5051,62 @@ var installBundle = async (staging, destination) => {
3539
5051
  }
3540
5052
  };
3541
5053
  var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) => {
3542
- if (page.framework !== "react") {
3543
- throw new TypeError(`Capacitor spike currently supports React pages; ${page.pageId} is ${page.framework}.`);
5054
+ if (!CAPACITOR_CLIENT_FRAMEWORKS.has(page.framework)) {
5055
+ throw new TypeError(`Capacitor client rendering does not yet support ${page.framework} page ${page.pageId}.`);
3544
5056
  }
3545
- const extension = extname(page.bundlePath) || ".js";
5057
+ const extension = extname2(page.bundlePath) || ".js";
3546
5058
  const localBundlePath = `./pages/${page.bundleHash}${extension}`;
3547
5059
  const source = sourceAssetPath(buildDirectory, page.bundlePath);
3548
- await copyFile5(source, join8(staging, localBundlePath));
5060
+ await copyFile5(source, join9(staging, localBundlePath));
3549
5061
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
3550
- return { ...page, localBundlePath };
5062
+ let localStylePath;
5063
+ if (page.styleBundlePath && page.styleBundleHash) {
5064
+ const styleExtension = extname2(page.styleBundlePath) || ".css";
5065
+ localStylePath = `./styles/${page.styleBundleHash}${styleExtension}`;
5066
+ const styleSource = sourceAssetPath(buildDirectory, page.styleBundlePath);
5067
+ await mkdir9(dirname7(join9(staging, localStylePath)), {
5068
+ recursive: true
5069
+ });
5070
+ await copyFile5(styleSource, join9(staging, localStylePath));
5071
+ await copyAbsoluteClientDependencies(styleSource, buildDirectory, staging, copiedDependencies);
5072
+ }
5073
+ return {
5074
+ ...page,
5075
+ localBundlePath,
5076
+ ...localStylePath ? { localStylePath } : {}
5077
+ };
3551
5078
  };
3552
- var absoluteClientImports = async (sourcePath) => {
3553
- const source = await readFile10(sourcePath, "utf8");
3554
- return [...source.matchAll(CLIENT_IMPORT_PATTERN)].flatMap((match) => {
3555
- const [specifier] = match.slice(1);
3556
- return specifier ? [specifier.split(/[?#]/u, 1)[0] ?? specifier] : [];
5079
+ var absoluteClientImports = async (sourcePath, buildDirectory) => {
5080
+ const source = await readFile11(sourcePath, "utf8");
5081
+ const extension = extname2(sourcePath).toLowerCase();
5082
+ let scriptLoader;
5083
+ if (extension === ".tsx")
5084
+ scriptLoader = "tsx";
5085
+ else if (extension === ".ts")
5086
+ scriptLoader = "ts";
5087
+ else if (extension === ".jsx")
5088
+ scriptLoader = "jsx";
5089
+ else if ([".js", ".mjs", ".cjs"].includes(extension))
5090
+ scriptLoader = "js";
5091
+ const scriptImports = scriptLoader ? new Bun.Transpiler({ loader: scriptLoader }).scanImports(source).map(({ path }) => path) : [];
5092
+ const cssImports = extension === ".css" ? [...source.matchAll(CLIENT_CSS_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
5093
+ const markupImports = extension === ".html" ? [...source.matchAll(CLIENT_MARKUP_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
5094
+ return [...scriptImports, ...cssImports, ...markupImports].flatMap((specifier) => {
5095
+ if (!specifier)
5096
+ return [];
5097
+ if (!specifier.startsWith("/") && !specifier.startsWith("./") && !specifier.startsWith("../")) {
5098
+ return [];
5099
+ }
5100
+ const clean = specifier.split(/[?#]/u, 1)[0] ?? specifier;
5101
+ if (clean.startsWith("/"))
5102
+ return [clean];
5103
+ const resolved = resolve9(dirname7(sourcePath), clean);
5104
+ const root = resolve9(buildDirectory);
5105
+ const relativePath = relative8(root, resolved).replaceAll("\\", "/");
5106
+ if (relativePath === ".." || relativePath.startsWith("../")) {
5107
+ throw new TypeError(`Mobile client dependency escaped the build directory: ${specifier}`);
5108
+ }
5109
+ return [`/${relativePath}`];
3557
5110
  });
3558
5111
  };
3559
5112
  var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, copied) => {
@@ -3561,13 +5114,13 @@ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, co
3561
5114
  return;
3562
5115
  copied.add(specifier);
3563
5116
  const source = sourceAssetPath(buildDirectory, specifier);
3564
- const destination = join8(staging, specifier.replace(/^\/+/, ""));
3565
- await mkdir7(dirname5(destination), { recursive: true });
5117
+ const destination = join9(staging, specifier.replace(/^\/+/, ""));
5118
+ await mkdir9(dirname7(destination), { recursive: true });
3566
5119
  await copyFile5(source, destination);
3567
5120
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
3568
5121
  };
3569
5122
  var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
3570
- const dependencies = await absoluteClientImports(sourcePath);
5123
+ const dependencies = await absoluteClientImports(sourcePath, buildDirectory);
3571
5124
  await Promise.all(dependencies.map((specifier) => copyAbsoluteClientDependency(specifier, buildDirectory, staging, copied)));
3572
5125
  };
3573
5126
  var materializeAbsoluteCapacitorWebBundle = async (options) => {
@@ -3575,31 +5128,51 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
3575
5128
  throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
3576
5129
  }
3577
5130
  const destination = options.config.bundleDirectory;
3578
- await mkdir7(dirname5(destination), { recursive: true });
3579
- const staging = await mkdtemp4(join8(dirname5(destination), `.${basename2(destination)}.stage-`));
5131
+ await mkdir9(dirname7(destination), { recursive: true });
5132
+ const staging = await mkdtemp4(join9(dirname7(destination), `.${basename3(destination)}.stage-`));
3580
5133
  try {
3581
- const pageDirectory = join8(staging, "pages");
3582
- await mkdir7(pageDirectory, { recursive: true });
5134
+ const pageDirectory = join9(staging, "pages");
5135
+ await mkdir9(pageDirectory, { recursive: true });
5136
+ await Promise.all(CLIENT_ASSET_DIRECTORIES.map((directory) => ({
5137
+ destination: join9(staging, directory),
5138
+ source: join9(options.buildDirectory, directory)
5139
+ })).filter(({ source }) => existsSync2(source)).map(({ destination: assetDestination, source }) => cp(source, assetDestination, { recursive: true })));
3583
5140
  const copiedDependencies = new Set;
3584
5141
  const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
3585
5142
  const manifest = {
3586
5143
  appBuild: options.artifact.appBuild,
5144
+ ...options.auth ? { auth: options.auth } : {},
3587
5145
  appId: options.config.appId,
3588
5146
  appName: options.config.appName,
3589
5147
  deepLinkHosts: options.config.deepLinkHosts,
3590
5148
  deepLinkScheme: options.config.deepLinkScheme,
5149
+ deviceCapabilities: options.deviceCapabilities.capabilities,
3591
5150
  entry: options.config.entry,
3592
5151
  format: ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
3593
5152
  pages,
3594
5153
  productionOrigin: options.config.productionOrigin,
3595
5154
  routes: options.artifact.routes,
3596
- runtime: options.artifact.runtime
5155
+ runtime: options.artifact.runtime,
5156
+ ...options.sync ? {
5157
+ sync: {
5158
+ background: {
5159
+ endpoint: new URL("/__absolute/sync/background", options.config.productionOrigin).href,
5160
+ intervalMinutes: 15
5161
+ },
5162
+ socketTickets: true,
5163
+ storageSchema: options.syncSchema ?? {
5164
+ components: [
5165
+ { id: "@absolutejs/app", version: 1 }
5166
+ ]
5167
+ }
5168
+ }
5169
+ } : {}
3597
5170
  };
3598
5171
  await Promise.all([
3599
- writeFile8(join8(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
5172
+ writeFile10(join9(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
3600
5173
  `),
3601
- writeFile8(join8(staging, INDEX_FILE), indexHtml(options.config.appName)),
3602
- buildShellBootstrap(staging)
5174
+ writeFile10(join9(staging, INDEX_FILE), indexHtml(options.config.appName)),
5175
+ buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.deviceCapabilities, options.projectRoot)
3603
5176
  ]);
3604
5177
  await installBundle(staging, destination);
3605
5178
  return manifest;
@@ -3610,17 +5183,17 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
3610
5183
  };
3611
5184
 
3612
5185
  // src/mobile/materializedBundle.ts
3613
- import { createHash as createHash8 } from "crypto";
5186
+ import { createHash as createHash9 } from "crypto";
3614
5187
  import {
3615
5188
  access as access8,
3616
- mkdir as mkdir8,
5189
+ mkdir as mkdir10,
3617
5190
  mkdtemp as mkdtemp5,
3618
- readFile as readFile11,
3619
- rename as rename9,
5191
+ readFile as readFile12,
5192
+ rename as rename10,
3620
5193
  rm as rm8,
3621
- writeFile as writeFile9
5194
+ writeFile as writeFile11
3622
5195
  } from "fs/promises";
3623
- import { dirname as dirname6, join as join9, resolve as resolvePath2 } from "path";
5196
+ import { dirname as dirname8, join as join10, resolve as resolvePath3 } from "path";
3624
5197
  import { pathToFileURL } from "url";
3625
5198
  var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
3626
5199
  var CURRENT_BUNDLE_FILE = "current.json";
@@ -3634,7 +5207,7 @@ var bundleIdFor = (currentReleaseId, releases) => {
3634
5207
  currentReleaseId,
3635
5208
  releases: releases.map(({ releaseId }) => releaseId)
3636
5209
  });
3637
- return `amb_${createHash8("sha256").update(identity).digest("hex")}`;
5210
+ return `amb_${createHash9("sha256").update(identity).digest("hex")}`;
3638
5211
  };
3639
5212
  var parseBundleIndex = (value) => {
3640
5213
  if (!isRecord7(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
@@ -3660,17 +5233,17 @@ var parseBundleIndex = (value) => {
3660
5233
  };
3661
5234
  };
3662
5235
  var writeRelease = async (root, release) => {
3663
- const directory = join9(root, release.artifact.releaseId);
3664
- const producerPath = join9(directory, release.artifact.producer.module);
3665
- await mkdir8(dirname6(producerPath), { recursive: true });
5236
+ const directory = join10(root, release.artifact.releaseId);
5237
+ const producerPath = join10(directory, release.artifact.producer.module);
5238
+ await mkdir10(dirname8(producerPath), { recursive: true });
3666
5239
  await Promise.all([
3667
- writeFile9(join9(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
5240
+ writeFile11(join10(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
3668
5241
  `),
3669
- writeFile9(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
5242
+ writeFile11(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
3670
5243
  ]);
3671
5244
  };
3672
5245
  var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
3673
- const destination = join9(bundlesRoot, bundleId);
5246
+ const destination = join10(bundlesRoot, bundleId);
3674
5247
  try {
3675
5248
  await access8(destination);
3676
5249
  return destination;
@@ -3678,10 +5251,10 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
3678
5251
  if (!errorHasCode3(error, "ENOENT"))
3679
5252
  throw error;
3680
5253
  }
3681
- const staging = await mkdtemp5(join9(bundlesRoot, ".stage-"));
5254
+ const staging = await mkdtemp5(join10(bundlesRoot, ".stage-"));
3682
5255
  try {
3683
5256
  await Promise.all(releases.map((release) => writeRelease(staging, release)));
3684
- await rename9(staging, destination);
5257
+ await rename10(staging, destination);
3685
5258
  } catch (error) {
3686
5259
  await rm8(staging, { force: true, recursive: true });
3687
5260
  if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
@@ -3711,16 +5284,16 @@ var resolveProducerHandler = (loaded, exportName) => {
3711
5284
  };
3712
5285
  };
3713
5286
  var loadAbsoluteMobileMaterializedBundle = async (root) => {
3714
- const resolvedRoot = resolvePath2(root);
3715
- const serialized = await readFile11(join9(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
5287
+ const resolvedRoot = resolvePath3(root);
5288
+ const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
3716
5289
  const parsed = JSON.parse(serialized);
3717
5290
  const index = parseBundleIndex(parsed);
3718
- const bundleRoot = join9(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
5291
+ const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
3719
5292
  return {
3720
5293
  artifacts: index.releases,
3721
5294
  currentReleaseId: index.currentReleaseId,
3722
5295
  loadProducer: async (artifact) => {
3723
- const modulePath = join9(bundleRoot, artifact.releaseId, artifact.producer.module);
5296
+ const modulePath = join10(bundleRoot, artifact.releaseId, artifact.producer.module);
3724
5297
  await verifyAbsoluteMobileCompatibilityProducer({
3725
5298
  artifact,
3726
5299
  producer: Bun.file(modulePath)
@@ -3746,9 +5319,9 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
3746
5319
  }
3747
5320
  return release;
3748
5321
  });
3749
- const root = resolvePath2(input.root);
3750
- const bundlesRoot = join9(root, BUNDLES_DIRECTORY);
3751
- await mkdir8(bundlesRoot, { recursive: true });
5322
+ const root = resolvePath3(input.root);
5323
+ const bundlesRoot = join10(root, BUNDLES_DIRECTORY);
5324
+ await mkdir10(bundlesRoot, { recursive: true });
3752
5325
  const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
3753
5326
  await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
3754
5327
  const index = {
@@ -3757,22 +5330,22 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
3757
5330
  format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
3758
5331
  releases: artifacts
3759
5332
  };
3760
- const pointerPath = join9(root, CURRENT_BUNDLE_FILE);
3761
- const temporaryPointerPath = join9(root, `.current-${crypto.randomUUID()}.json`);
3762
- await writeFile9(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
5333
+ const pointerPath = join10(root, CURRENT_BUNDLE_FILE);
5334
+ const temporaryPointerPath = join10(root, `.current-${crypto.randomUUID()}.json`);
5335
+ await writeFile11(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
3763
5336
  `, { flag: "wx" });
3764
- await rename9(temporaryPointerPath, pointerPath);
5337
+ await rename10(temporaryPointerPath, pointerPath);
3765
5338
  return index;
3766
5339
  };
3767
5340
  var readAbsoluteMobileMaterializedReleases = async (root) => {
3768
- const resolvedRoot = resolvePath2(root);
5341
+ const resolvedRoot = resolvePath3(root);
3769
5342
  try {
3770
- const serialized = await readFile11(join9(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
5343
+ const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
3771
5344
  const parsed = JSON.parse(serialized);
3772
5345
  const index = parseBundleIndex(parsed);
3773
- const bundleRoot = join9(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
5346
+ const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
3774
5347
  return Promise.all(index.releases.map(async (artifact) => {
3775
- const producer = Bun.file(join9(bundleRoot, artifact.releaseId, artifact.producer.module));
5348
+ const producer = Bun.file(join10(bundleRoot, artifact.releaseId, artifact.producer.module));
3776
5349
  await verifyAbsoluteMobileCompatibilityProducer({
3777
5350
  artifact,
3778
5351
  producer
@@ -3786,7 +5359,61 @@ var readAbsoluteMobileMaterializedReleases = async (root) => {
3786
5359
  }
3787
5360
  };
3788
5361
 
5362
+ // src/mobile/nativeAuth.ts
5363
+ import { readFileSync as readFileSync2 } from "fs";
5364
+ import { join as join11 } from "path";
5365
+ var ABSOLUTE_AUTH_PACKAGE = "@absolutejs/auth";
5366
+ var ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV = "ABSOLUTE_AUTH_NATIVE_CLIENTS";
5367
+ var ABSOLUTE_NATIVE_AUTH_SCOPES = ["openid", "profile"];
5368
+ var ABSOLUTE_SYNC_PACKAGE = "@absolutejs/sync";
5369
+ var readPackageManifest = (projectRoot) => {
5370
+ try {
5371
+ return JSON.parse(readFileSync2(join11(projectRoot, "package.json"), "utf8"));
5372
+ } catch {
5373
+ return;
5374
+ }
5375
+ };
5376
+ var packageManifestHas = (manifest, packageName) => {
5377
+ if (typeof manifest !== "object" || manifest === null)
5378
+ return false;
5379
+ return [
5380
+ Reflect.get(manifest, "dependencies"),
5381
+ Reflect.get(manifest, "devDependencies"),
5382
+ Reflect.get(manifest, "optionalDependencies"),
5383
+ Reflect.get(manifest, "peerDependencies")
5384
+ ].some((dependencies) => typeof dependencies === "object" && dependencies !== null && Object.hasOwn(dependencies, packageName));
5385
+ };
5386
+ var createAbsoluteMobileAuthManifest = (config) => {
5387
+ const scheme = config.deepLinkScheme ?? config.appId.toLowerCase();
5388
+ return {
5389
+ clientId: `absolutejs-native:${config.appId}`,
5390
+ issuer: config.productionOrigin,
5391
+ redirectUri: `${scheme}://auth/callback`,
5392
+ scopes: [...ABSOLUTE_NATIVE_AUTH_SCOPES]
5393
+ };
5394
+ };
5395
+ var installAbsoluteMobileAuthEnvironment = (projectRoot, config) => {
5396
+ const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
5397
+ const serialized = serializeAbsoluteMobileAuthEnvironment(config, auth);
5398
+ if (serialized === undefined)
5399
+ delete process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV];
5400
+ else
5401
+ process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV] = serialized;
5402
+ return auth;
5403
+ };
5404
+ var projectUsesAbsoluteAuth = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_AUTH_PACKAGE);
5405
+ var projectUsesAbsoluteSync = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_SYNC_PACKAGE);
5406
+ var resolveAbsoluteMobileAuthManifest = (projectRoot, config) => projectUsesAbsoluteAuth(projectRoot) ? createAbsoluteMobileAuthManifest(config) : undefined;
5407
+ var serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefined ? undefined : JSON.stringify([
5408
+ {
5409
+ ...auth,
5410
+ name: `${config.appName} native app`
5411
+ }
5412
+ ]);
5413
+
3789
5414
  // src/mobile/buildPipeline.ts
5415
+ init_syncSchema();
5416
+ init_deviceCapabilities();
3790
5417
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
3791
5418
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
3792
5419
  var serverExportName = (loaded, app) => {
@@ -3796,12 +5423,11 @@ var serverExportName = (loaded, app) => {
3796
5423
  return "app";
3797
5424
  return "default";
3798
5425
  };
3799
- var restoreBuildDirectory = (previous) => {
3800
- if (previous !== undefined) {
3801
- process.env.ABSOLUTE_BUILD_DIR = previous;
3802
- return;
3803
- }
3804
- delete process.env.ABSOLUTE_BUILD_DIR;
5426
+ var restoreEnvironmentVariable = (name, previous) => {
5427
+ if (previous !== undefined)
5428
+ process.env[name] = previous;
5429
+ else
5430
+ delete process.env[name];
3805
5431
  };
3806
5432
  var requireRelease = (releases, releaseId) => {
3807
5433
  const release = releases.get(releaseId);
@@ -3821,11 +5447,11 @@ var loadServerApp = async (producerPath) => {
3821
5447
  return { app, exportName };
3822
5448
  };
3823
5449
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
3824
- const buildDirectory = resolve10(options.buildDirectory);
5450
+ const buildDirectory = resolve12(options.buildDirectory);
3825
5451
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
3826
- const root = join10(buildDirectory, ".absolutejs", "mobile-compatibility");
5452
+ const root = join14(buildDirectory, ".absolutejs", "mobile-compatibility");
3827
5453
  const [manifestSource, previous] = await Promise.all([
3828
- readFile12(join10(buildDirectory, "manifest.json"), "utf8"),
5454
+ readFile13(join14(buildDirectory, "manifest.json"), "utf8"),
3829
5455
  readAbsoluteMobileMaterializedReleases(root)
3830
5456
  ]);
3831
5457
  const manifest = JSON.parse(manifestSource);
@@ -3833,12 +5459,20 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
3833
5459
  throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
3834
5460
  }
3835
5461
  const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
5462
+ const previousCompiledRuntime = process.env.ABSOLUTE_COMPILED_RUNTIME;
5463
+ const previousConfigPath = process.env.ABSOLUTE_CONFIG;
3836
5464
  process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
5465
+ process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
5466
+ if (options.configPath) {
5467
+ process.env.ABSOLUTE_CONFIG = resolve12(options.projectRoot, options.configPath);
5468
+ }
3837
5469
  let loaded;
3838
5470
  try {
3839
- loaded = await loadServerApp(resolve10(options.producerPath));
5471
+ loaded = await loadServerApp(resolve12(options.producerPath));
3840
5472
  } finally {
3841
- restoreBuildDirectory(previousBuildDirectory);
5473
+ restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
5474
+ restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
5475
+ restoreEnvironmentVariable("ABSOLUTE_CONFIG", previousConfigPath);
3842
5476
  }
3843
5477
  const current = await buildAbsoluteMobileCompatibilityRelease({
3844
5478
  app: loaded.app,
@@ -3847,9 +5481,22 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
3847
5481
  manifest,
3848
5482
  previousArtifacts: previous.map(({ artifact }) => artifact),
3849
5483
  producerExport: loaded.exportName,
3850
- producerPath: resolve10(options.producerPath),
5484
+ producerPath: resolve12(options.producerPath),
3851
5485
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
3852
5486
  });
5487
+ const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
5488
+ const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
5489
+ const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
5490
+ const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot);
5491
+ const usesPush = deviceCapabilities.capabilities.includes("pushNotifications");
5492
+ if (usesPush && !auth)
5493
+ throw new TypeError("Portable push notifications require @absolutejs/auth so provider tokens can be registered without exposing identity controls to page code.");
5494
+ if (usesPush && !loaded.app.routes.some((route) => route.path === "/auth/push" || route.path === "/auth/mobile/push"))
5495
+ throw new TypeError("@absolutejs/devices pushNotifications is used, but Auth push is not configured. Pass a trusted server-side registrar to auth({ push: ... }).");
5496
+ assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
5497
+ if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
5498
+ throw new TypeError("@absolutejs/auth is installed, but its OIDC provider is not mounted. Native authentication requires the auth oidc configuration so AbsoluteJS can provision a public PKCE client.");
5499
+ }
3853
5500
  const releasesById = new Map([current, ...previous].map((release) => [
3854
5501
  release.artifact.releaseId,
3855
5502
  release
@@ -3862,11 +5509,131 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
3862
5509
  });
3863
5510
  await materializeAbsoluteCapacitorWebBundle({
3864
5511
  artifact: current.artifact,
5512
+ ...auth ? { auth } : {},
3865
5513
  buildDirectory,
3866
- config: mobile
5514
+ config: mobile,
5515
+ deviceCapabilities,
5516
+ projectRoot: options.projectRoot,
5517
+ ...sync ? { sync: true } : {},
5518
+ ...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
3867
5519
  });
3868
5520
  return current.artifact;
3869
5521
  };
5522
+
5523
+ // src/mobile/index.ts
5524
+ init_syncSchema();
5525
+
5526
+ // node_modules/@absolutejs/sync/dist/client/runtimeTransport.js
5527
+ var RUNTIME_TRANSPORT2 = Symbol.for("@absolutejs/sync/client-runtime-transport");
5528
+ var host2 = globalThis;
5529
+ var isRegistry2 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients"));
5530
+ var registry2 = (() => {
5531
+ const existing = host2[RUNTIME_TRANSPORT2];
5532
+ if (isRegistry2(existing))
5533
+ return existing;
5534
+ if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
5535
+ Reflect.set(existing, "clients", []);
5536
+ return existing;
5537
+ }
5538
+ const created = { clients: [], installations: [] };
5539
+ Object.defineProperty(host2, RUNTIME_TRANSPORT2, {
5540
+ configurable: false,
5541
+ enumerable: false,
5542
+ value: created,
5543
+ writable: false
5544
+ });
5545
+ return created;
5546
+ })();
5547
+ var maximum = (values) => {
5548
+ const present = values.filter((value) => value !== undefined);
5549
+ return present.length === 0 ? undefined : Math.max(...present);
5550
+ };
5551
+ var minimum = (values) => {
5552
+ const present = values.filter((value) => value !== undefined);
5553
+ return present.length === 0 ? undefined : Math.min(...present);
5554
+ };
5555
+ var inspectSyncRuntime = async () => {
5556
+ const clients = [...registry2.clients];
5557
+ const statuses = clients.map((client) => client.status());
5558
+ const deadLetters = (await Promise.all(clients.map((client) => client.listDeadLetters()))).flat().map((record) => ({
5559
+ attempts: record.attempts,
5560
+ ...record.rejection?.code ? { code: record.rejection.code } : {},
5561
+ createdAt: record.createdAt,
5562
+ ...record.deadLetteredAt === undefined ? {} : { deadLetteredAt: record.deadLetteredAt },
5563
+ ...record.rejection?.kind ? { kind: record.rejection.kind } : {},
5564
+ ...record.rejection?.message ? { message: record.rejection.message } : {},
5565
+ name: record.name,
5566
+ operationId: record.operationId
5567
+ })).sort((left, right) => (left.deadLetteredAt ?? left.createdAt) - (right.deadLetteredAt ?? right.createdAt) || left.operationId.localeCompare(right.operationId));
5568
+ const lastError = statuses.findLast((status) => status.lastError)?.lastError;
5569
+ return {
5570
+ automaticResolutions: statuses.reduce((total, status) => total + status.automaticResolutions, 0),
5571
+ clients: clients.length,
5572
+ conflicts: deadLetters.filter((record) => record.kind === "conflict").length,
5573
+ deadLetters,
5574
+ ...lastError ? { lastError } : {},
5575
+ ...maximum(statuses.map((status) => status.lastSuccessfulPullAt)) === undefined ? {} : {
5576
+ lastSuccessfulPullAt: maximum(statuses.map((status) => status.lastSuccessfulPullAt))
5577
+ },
5578
+ ...maximum(statuses.map((status) => status.lastSuccessfulPushAt)) === undefined ? {} : {
5579
+ lastSuccessfulPushAt: maximum(statuses.map((status) => status.lastSuccessfulPushAt))
5580
+ },
5581
+ ...minimum(statuses.map((status) => status.oldestDeadLetterAt)) === undefined ? {} : {
5582
+ oldestDeadLetterAt: minimum(statuses.map((status) => status.oldestDeadLetterAt))
5583
+ },
5584
+ ...minimum(statuses.map((status) => status.oldestPendingAt)) === undefined ? {} : {
5585
+ oldestPendingAt: minimum(statuses.map((status) => status.oldestPendingAt))
5586
+ },
5587
+ pending: statuses.reduce((total, status) => total + status.pending, 0)
5588
+ };
5589
+ };
5590
+ var clientWithDeadLetter = async (operationId) => {
5591
+ for (const client of registry2.clients)
5592
+ if ((await client.listDeadLetters()).some((record) => record.operationId === operationId))
5593
+ return client;
5594
+ throw new Error(`Unknown Sync dead letter "${operationId}"`);
5595
+ };
5596
+ var retrySyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).retryDeadLetter(operationId);
5597
+ var discardSyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).discardDeadLetter(operationId);
5598
+ var rebaseSyncRuntimeDeadLetter = async (operationId, args) => (await clientWithDeadLetter(operationId)).rebaseDeadLetter(operationId, args);
5599
+
5600
+ // src/mobile/syncRemediation.ts
5601
+ var REMEDIATION_REGISTRY = Symbol.for("@absolutejs/mobile-sync-remediation");
5602
+ var LAST_INSTALLATION_OFFSET = -1;
5603
+ var isRegistry3 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
5604
+ var resolveRegistry = () => {
5605
+ const existing = Reflect.get(globalThis, REMEDIATION_REGISTRY);
5606
+ if (isRegistry3(existing))
5607
+ return existing;
5608
+ const created = { installations: [] };
5609
+ Object.defineProperty(globalThis, REMEDIATION_REGISTRY, {
5610
+ configurable: false,
5611
+ enumerable: false,
5612
+ value: created,
5613
+ writable: false
5614
+ });
5615
+ return created;
5616
+ };
5617
+ var registry3 = resolveRegistry();
5618
+ var getAbsoluteMobileSyncRemediation = () => registry3.installations.at(LAST_INSTALLATION_OFFSET)?.bridge;
5619
+ var installAbsoluteMobileSyncRemediation = (bridge = {
5620
+ discard: discardSyncRuntimeDeadLetter,
5621
+ inspect: inspectSyncRuntime,
5622
+ rebase: rebaseSyncRuntimeDeadLetter,
5623
+ retry: retrySyncRuntimeDeadLetter
5624
+ }) => {
5625
+ const installation = { bridge };
5626
+ registry3.installations.push(installation);
5627
+ return () => {
5628
+ const index = registry3.installations.indexOf(installation);
5629
+ if (index >= 0)
5630
+ registry3.installations.splice(index, 1);
5631
+ };
5632
+ };
5633
+
5634
+ // src/mobile/index.ts
5635
+ init_deviceCapabilities();
5636
+
3870
5637
  // src/mobile/compatibilityDispatcher.ts
3871
5638
  import { Elysia as Elysia2 } from "elysia";
3872
5639
 
@@ -3888,6 +5655,68 @@ var ensureProducerStorage = () => {
3888
5655
  var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
3889
5656
 
3890
5657
  // src/mobile/compatibilityDispatcher.ts
5658
+ var MOBILE_WEBVIEW_ORIGINS = new Set([
5659
+ "capacitor://localhost",
5660
+ "http://localhost",
5661
+ "https://localhost"
5662
+ ]);
5663
+ var MOBILE_REQUEST_HEADER_NAMES = Object.values(MOBILE_PAGE_REQUEST_HEADERS);
5664
+ var MOBILE_CORS_ALLOW_HEADERS = [
5665
+ "accept",
5666
+ "content-type",
5667
+ "authorization",
5668
+ "hx-current-url",
5669
+ "hx-request",
5670
+ "hx-target",
5671
+ "hx-trigger",
5672
+ "hx-trigger-name",
5673
+ ...MOBILE_REQUEST_HEADER_NAMES
5674
+ ].join(", ");
5675
+ var MOBILE_CORS_METHODS = new Set([
5676
+ "DELETE",
5677
+ "GET",
5678
+ "HEAD",
5679
+ "OPTIONS",
5680
+ "PATCH",
5681
+ "POST",
5682
+ "PUT"
5683
+ ]);
5684
+ var mobileWebViewOrigin = (request) => {
5685
+ const origin = request.headers.get("origin");
5686
+ return origin && MOBILE_WEBVIEW_ORIGINS.has(origin) ? origin : undefined;
5687
+ };
5688
+ var applyMobileCorsHeaders = (response, origin) => {
5689
+ response.headers.set("access-control-allow-credentials", "true");
5690
+ response.headers.set("access-control-allow-origin", origin);
5691
+ response.headers.append("vary", "Origin");
5692
+ return response;
5693
+ };
5694
+ var finalizeMobileResponse = (request, response) => {
5695
+ const origin = mobileWebViewOrigin(request);
5696
+ return origin ? applyMobileCorsHeaders(response, origin) : response;
5697
+ };
5698
+ var mobilePreflightResponse = (request) => {
5699
+ if (request.method !== "OPTIONS")
5700
+ return;
5701
+ const origin = mobileWebViewOrigin(request);
5702
+ if (!origin)
5703
+ return;
5704
+ const requestedHeaders = request.headers.get("access-control-request-headers");
5705
+ const requestedMethod = request.headers.get("access-control-request-method")?.toUpperCase() ?? "";
5706
+ if (!MOBILE_CORS_METHODS.has(requestedMethod))
5707
+ return;
5708
+ return new Response(null, {
5709
+ headers: {
5710
+ "access-control-allow-credentials": "true",
5711
+ "access-control-allow-headers": requestedHeaders || MOBILE_CORS_ALLOW_HEADERS,
5712
+ "access-control-allow-methods": [...MOBILE_CORS_METHODS].join(", "),
5713
+ "access-control-allow-origin": origin,
5714
+ "access-control-max-age": "600",
5715
+ vary: "Origin, Access-Control-Request-Headers"
5716
+ },
5717
+ status: 204
5718
+ });
5719
+ };
3891
5720
  var artifactOwnsRequest = (artifact, pageId, request) => {
3892
5721
  const { pathname } = new URL(request.url);
3893
5722
  return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
@@ -3915,46 +5744,55 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
3915
5744
  return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
3916
5745
  if (getCurrentAbsoluteMobileProducerContext())
3917
5746
  return;
5747
+ const preflight = mobilePreflightResponse(request);
5748
+ if (preflight)
5749
+ return preflight;
3918
5750
  const parsed = parseAbsoluteMobilePageRequest(request);
3919
5751
  if (parsed.kind !== "mobile")
3920
5752
  return;
3921
5753
  const resolved = resolveAbsoluteMobileCompatibilityRelease(parsed.client, artifacts);
3922
5754
  if (resolved.kind === "upgrade-required") {
3923
- return createAbsoluteMobileUpgradeResponse(resolved.result);
5755
+ return finalizeMobileResponse(request, createAbsoluteMobileUpgradeResponse(resolved.result));
3924
5756
  }
3925
5757
  if (!artifactOwnsRequest(resolved.artifact, parsed.client.pageId, request)) {
3926
- return createAbsoluteMobileInvalidRequestResponse("The requested URL is not assigned to this mobile page.");
5758
+ return finalizeMobileResponse(request, createAbsoluteMobileInvalidRequestResponse("The requested URL is not assigned to this mobile page."));
3927
5759
  }
3928
5760
  if (resolved.artifact.releaseId === options.currentReleaseId) {
3929
5761
  return;
3930
5762
  }
3931
5763
  try {
3932
5764
  const producer = await resolveProducer(resolved.artifact);
3933
- return runWithAbsoluteMobileProducer({
5765
+ const response = await runWithAbsoluteMobileProducer({
3934
5766
  page: resolved.page,
3935
5767
  releaseId: resolved.artifact.releaseId
3936
5768
  }, () => producer.handle(request));
5769
+ return finalizeMobileResponse(request, response);
3937
5770
  } catch (error) {
3938
5771
  console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
3939
- return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
5772
+ return finalizeMobileResponse(request, createAbsoluteMobilePageErrorResponse(parsed.client.pageId));
3940
5773
  }
5774
+ }).afterHandle("global", ({ request, responseValue }) => {
5775
+ const origin = mobileWebViewOrigin(request);
5776
+ if (!origin || !(responseValue instanceof Response))
5777
+ return;
5778
+ applyMobileCorsHeaders(responseValue, origin);
3941
5779
  }).as("global");
3942
5780
  };
3943
5781
  // src/mobile/nativeDeepLinks.ts
3944
- import { readFile as readFile13, rename as rename10, writeFile as writeFile10 } from "fs/promises";
3945
- import { join as join11 } from "path";
5782
+ import { readFile as readFile14, rename as rename11, writeFile as writeFile12 } from "fs/promises";
5783
+ import { join as join15 } from "path";
3946
5784
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
3947
5785
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
3948
5786
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
3949
5787
  var NOT_FOUND = -1;
3950
5788
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3951
5789
  var writeChangedFile = async (path, source) => {
3952
- const current = await readFile13(path, "utf8");
5790
+ const current = await readFile14(path, "utf8");
3953
5791
  if (current === source)
3954
5792
  return false;
3955
5793
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
3956
- await writeFile10(temporary, source, { flag: "wx" });
3957
- await rename10(temporary, path);
5794
+ await writeFile12(temporary, source, { flag: "wx" });
5795
+ await rename11(temporary, path);
3958
5796
  return true;
3959
5797
  };
3960
5798
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -3978,7 +5816,7 @@ var replaceManagedRegion = (source, region, insertAt) => {
3978
5816
  return `${source.slice(0, index)}${region}${source.slice(index)}`;
3979
5817
  };
3980
5818
  var androidRegion = (config) => {
3981
- const hosts = config.deepLinkHosts.map((host) => ` <data android:scheme="https" android:host="${escapeXml(host)}" />`).join(`
5819
+ const hosts = config.deepLinkHosts.map((host3) => ` <data android:scheme="https" android:host="${escapeXml(host3)}" />`).join(`
3982
5820
  `);
3983
5821
  const customScheme = config.deepLinkScheme ? `
3984
5822
 
@@ -3999,8 +5837,8 @@ ${hosts}
3999
5837
  `;
4000
5838
  };
4001
5839
  var configureAndroid = async (config) => {
4002
- const path = join11(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
4003
- const source = await readFile13(path, "utf8");
5840
+ const path = join15(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
5841
+ const source = await readFile14(path, "utf8");
4004
5842
  const mainActivity = source.indexOf('android:name=".MainActivity"');
4005
5843
  if (mainActivity === NOT_FOUND) {
4006
5844
  throw new TypeError("Android MainActivity was not found.");
@@ -4025,8 +5863,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
4025
5863
  ${END_MARKER}
4026
5864
  `;
4027
5865
  var configureIosInfo = async (config) => {
4028
- const path = join11(config.nativeProjectDirectory, "ios/App/App/Info.plist");
4029
- const source = await readFile13(path, "utf8");
5866
+ const path = join15(config.nativeProjectDirectory, "ios/App/App/Info.plist");
5867
+ const source = await readFile14(path, "utf8");
4030
5868
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
4031
5869
  ${END_MARKER}
4032
5870
  `;
@@ -4034,7 +5872,7 @@ var configureIosInfo = async (config) => {
4034
5872
  return writeChangedFile(path, updated);
4035
5873
  };
4036
5874
  var iosEntitlementsSource = (config) => {
4037
- const domains = config.deepLinkHosts.map((host) => ` <string>applinks:${escapeXml(host)}</string>`).join(`
5875
+ const domains = config.deepLinkHosts.map((host3) => ` <string>applinks:${escapeXml(host3)}</string>`).join(`
4038
5876
  `);
4039
5877
  return `<?xml version="1.0" encoding="UTF-8"?>
4040
5878
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -4049,10 +5887,10 @@ ${domains}
4049
5887
  `;
4050
5888
  };
4051
5889
  var configureIosEntitlements = async (config) => {
4052
- const path = join11(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
5890
+ const path = join15(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
4053
5891
  let current = "";
4054
5892
  try {
4055
- current = await readFile13(path, "utf8");
5893
+ current = await readFile14(path, "utf8");
4056
5894
  } catch (error) {
4057
5895
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
4058
5896
  throw error;
@@ -4062,13 +5900,13 @@ var configureIosEntitlements = async (config) => {
4062
5900
  if (current === source)
4063
5901
  return false;
4064
5902
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
4065
- await writeFile10(temporary, source, { flag: "wx" });
4066
- await rename10(temporary, path);
5903
+ await writeFile12(temporary, source, { flag: "wx" });
5904
+ await rename11(temporary, path);
4067
5905
  return true;
4068
5906
  };
4069
5907
  var configureIosProject = async (config) => {
4070
- const path = join11(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
4071
- const source = await readFile13(path, "utf8");
5908
+ const path = join15(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
5909
+ const source = await readFile14(path, "utf8");
4072
5910
  const declarations = [
4073
5911
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
4074
5912
  ].map((match) => match[1]);
@@ -4105,9 +5943,357 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
4105
5943
  changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
4106
5944
  };
4107
5945
  };
5946
+ // src/mobile/nativeDeviceCapabilities.ts
5947
+ init_deviceCapabilities();
5948
+ import { readFile as readFile15, rename as rename12, writeFile as writeFile13 } from "fs/promises";
5949
+ import { join as join16 } from "path";
5950
+ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
5951
+ var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
5952
+ var NOT_FOUND2 = -1;
5953
+ var IOS_PRIVACY_FILE_REFERENCE = "A85D0C000000000000000001";
5954
+ var IOS_PRIVACY_BUILD_FILE = "A85D0C000000000000000002";
5955
+ var PUSH_START_MARKER = "absolutejs:push-notifications:start";
5956
+ var PUSH_END_MARKER = "absolutejs:push-notifications:end";
5957
+ var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5958
+ var writeChangedFile2 = async (path, source) => {
5959
+ const current = await readFile15(path, "utf8");
5960
+ if (current === source)
5961
+ return false;
5962
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
5963
+ await writeFile13(temporary, source, { flag: "wx" });
5964
+ await rename12(temporary, path);
5965
+ return true;
5966
+ };
5967
+ var writeOptionalChangedFile = async (path, source) => {
5968
+ const current = await optionalSource(path);
5969
+ if (current === source)
5970
+ return false;
5971
+ if (current === null) {
5972
+ await writeFile13(path, source, { flag: "wx" });
5973
+ return true;
5974
+ }
5975
+ return writeChangedFile2(path, source);
5976
+ };
5977
+ var optionalSource = async (path) => {
5978
+ try {
5979
+ return await readFile15(path, "utf8");
5980
+ } catch (error) {
5981
+ if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
5982
+ return null;
5983
+ throw error;
5984
+ }
5985
+ };
5986
+ var managed = (source, region, insertion) => {
5987
+ const start = source.indexOf(START_MARKER2);
5988
+ const end = source.indexOf(END_MARKER2);
5989
+ if (start === NOT_FOUND2 !== (end === NOT_FOUND2) || start !== NOT_FOUND2 && end < start)
5990
+ throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
5991
+ if (start !== NOT_FOUND2) {
5992
+ const lineStart = source.lastIndexOf(`
5993
+ `, start) + 1;
5994
+ const nextLine = source.indexOf(`
5995
+ `, end + END_MARKER2.length);
5996
+ const lineEnd = nextLine === NOT_FOUND2 ? source.length : nextLine + 1;
5997
+ return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
5998
+ }
5999
+ if (region.length === 0)
6000
+ return source;
6001
+ if (insertion === NOT_FOUND2)
6002
+ throw new TypeError("Could not find a safe native project location for device permissions.");
6003
+ return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
6004
+ };
6005
+ var IOS_KEYS = {
6006
+ camera: "NSCameraUsageDescription",
6007
+ "location-always": "NSLocationAlwaysAndWhenInUseUsageDescription",
6008
+ "location-when-in-use": "NSLocationWhenInUseUsageDescription",
6009
+ "photo-library": "NSPhotoLibraryUsageDescription",
6010
+ "photo-library-add": "NSPhotoLibraryAddUsageDescription"
6011
+ };
6012
+ var iosDescription = (appName, purpose) => {
6013
+ if (purpose === "camera")
6014
+ return `${appName} uses your camera when you choose to take a photo.`;
6015
+ if (purpose === "photo-library")
6016
+ return `${appName} accesses your photo library only for photo actions you choose.`;
6017
+ if (purpose === "location-when-in-use")
6018
+ return `${appName} uses your location only while you are using the app and request a location-based action.`;
6019
+ if (purpose === "location-always")
6020
+ return `${appName} does not track location in the background; this description supports the foreground location provider required by the native runtime.`;
6021
+ return `${appName} adds to your photo library only for photo actions you choose.`;
6022
+ };
6023
+ var privacyEntries = (requirements) => requirements.iosPrivacyAccessedApis.map(({ api, reasons }) => ` <dict>
6024
+ <key>NSPrivacyAccessedAPIType</key>
6025
+ <string>${escapeXml2(api)}</string>
6026
+ <key>NSPrivacyAccessedAPITypeReasons</key>
6027
+ <array>
6028
+ ${reasons.map((reason) => ` <string>${escapeXml2(reason)}</string>`).join(`
6029
+ `)}
6030
+ </array>
6031
+ </dict>`).join(`
6032
+ `);
6033
+ var privacyManifestSource = (source, requirements) => {
6034
+ const entries = privacyEntries(requirements);
6035
+ const wholeRegion = entries ? ` ${START_MARKER2}
6036
+ <key>NSPrivacyAccessedAPITypes</key>
6037
+ <array>
6038
+ ${entries}
6039
+ </array>
6040
+ ${END_MARKER2}
6041
+ ` : "";
6042
+ if (source === null)
6043
+ return entries ? `<?xml version="1.0" encoding="UTF-8"?>
6044
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
6045
+ <plist version="1.0">
6046
+ <dict>
6047
+ ${wholeRegion} <key>NSPrivacyCollectedDataTypes</key>
6048
+ <array/>
6049
+ <key>NSPrivacyTracking</key>
6050
+ <false/>
6051
+ </dict>
6052
+ </plist>
6053
+ ` : null;
6054
+ const start = source.indexOf(START_MARKER2);
6055
+ if (start !== NOT_FOUND2) {
6056
+ const end = source.indexOf(END_MARKER2);
6057
+ if (end === NOT_FOUND2)
6058
+ throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
6059
+ const owned = source.slice(start, end);
6060
+ const ownsWholeKey = owned.includes("NSPrivacyAccessedAPITypes");
6061
+ let region = "";
6062
+ if (ownsWholeKey)
6063
+ region = wholeRegion;
6064
+ else if (entries)
6065
+ region = ` ${START_MARKER2}
6066
+ ${entries}
6067
+ ${END_MARKER2}
6068
+ `;
6069
+ return managed(source, region, source.lastIndexOf("</dict>"));
6070
+ }
6071
+ const key = source.indexOf("<key>NSPrivacyAccessedAPITypes</key>");
6072
+ if (key === NOT_FOUND2)
6073
+ return managed(source, wholeRegion, source.lastIndexOf("</dict>"));
6074
+ if (!entries)
6075
+ return source;
6076
+ const array = source.indexOf("<array>", key);
6077
+ if (array === NOT_FOUND2)
6078
+ throw new TypeError("iOS PrivacyInfo.xcprivacy has a malformed NSPrivacyAccessedAPITypes value.");
6079
+ const insertion = source.indexOf(`
6080
+ `, array);
6081
+ if (insertion === NOT_FOUND2)
6082
+ throw new TypeError("iOS PrivacyInfo.xcprivacy array is malformed.");
6083
+ return managed(source, ` ${START_MARKER2}
6084
+ ${entries}
6085
+ ${END_MARKER2}
6086
+ `, insertion + 1);
6087
+ };
6088
+ var writeIosPrivacyManifest = async (path, current, source) => {
6089
+ if (source === null)
6090
+ return false;
6091
+ if (current !== null)
6092
+ return writeChangedFile2(path, source);
6093
+ await writeFile13(path, source, { flag: "wx" });
6094
+ return true;
6095
+ };
6096
+ var configureIosPrivacyProject = async (config, requirements) => {
6097
+ if (requirements.iosPrivacyAccessedApis.length === 0)
6098
+ return false;
6099
+ const projectPath = join16(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
6100
+ const project = await readFile15(projectPath, "utf8");
6101
+ return writeChangedFile2(projectPath, addIosPrivacyProjectReference(project));
6102
+ };
6103
+ var addIosPrivacyProjectReference = (source) => {
6104
+ const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
6105
+ const fileReference = fileMatch?.[1] ?? IOS_PRIVACY_FILE_REFERENCE;
6106
+ const buildMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy in Resources \*\/ = \{isa = PBXBuildFile;/u);
6107
+ const buildFile = buildMatch?.[1] ?? IOS_PRIVACY_BUILD_FILE;
6108
+ if (!fileMatch && source.includes(fileReference) || !buildMatch && source.includes(buildFile))
6109
+ throw new TypeError("AbsoluteJS iOS privacy-manifest identifiers collide.");
6110
+ let next = source;
6111
+ if (!buildMatch) {
6112
+ const marker = "/* End PBXBuildFile section */";
6113
+ const index = next.indexOf(marker);
6114
+ if (index === NOT_FOUND2)
6115
+ throw new TypeError("Could not find the iOS PBXBuildFile section.");
6116
+ next = `${next.slice(0, index)} ${buildFile} /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = ${fileReference} /* PrivacyInfo.xcprivacy */; };
6117
+ ${next.slice(index)}`;
6118
+ }
6119
+ if (!fileMatch) {
6120
+ const marker = "/* End PBXFileReference section */";
6121
+ const index = next.indexOf(marker);
6122
+ if (index === NOT_FOUND2)
6123
+ throw new TypeError("Could not find the iOS PBXFileReference section.");
6124
+ next = `${next.slice(0, index)} ${fileReference} /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
6125
+ ${next.slice(index)}`;
6126
+ }
6127
+ const groupsStart = next.indexOf("/* Begin PBXGroup section */");
6128
+ const groupsEnd = next.indexOf("/* End PBXGroup section */");
6129
+ const groups = next.slice(groupsStart, groupsEnd);
6130
+ if (!groups.includes(`${fileReference} /* PrivacyInfo.xcprivacy */`)) {
6131
+ const appGroup = groups.match(/[A-F0-9]{24} \/\* App \*\/ = \{\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = \(\n/u);
6132
+ if (!appGroup || appGroup.index === undefined)
6133
+ throw new TypeError("Could not find the iOS App PBXGroup.");
6134
+ const index = groupsStart + appGroup.index + appGroup[0].length;
6135
+ next = `${next.slice(0, index)} ${fileReference} /* PrivacyInfo.xcprivacy */,
6136
+ ${next.slice(index)}`;
6137
+ }
6138
+ const resourcesStart = next.indexOf("/* Begin PBXResourcesBuildPhase section */");
6139
+ const resourcesEnd = next.indexOf("/* End PBXResourcesBuildPhase section */");
6140
+ const resources = next.slice(resourcesStart, resourcesEnd);
6141
+ if (!resources.includes(`${buildFile} /* PrivacyInfo.xcprivacy in Resources */`)) {
6142
+ const files = resources.match(/isa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = \d+;\n\t\t\tfiles = \(\n/u);
6143
+ if (!files || files.index === undefined)
6144
+ throw new TypeError("Could not find the iOS Resources build phase.");
6145
+ const index = resourcesStart + files.index + files[0].length;
6146
+ next = `${next.slice(0, index)} ${buildFile} /* PrivacyInfo.xcprivacy in Resources */,
6147
+ ${next.slice(index)}`;
6148
+ }
6149
+ return next;
6150
+ };
6151
+ var configureIos2 = async (config, plan) => {
6152
+ const path = join16(config.nativeProjectDirectory, "ios/App/App/Info.plist");
6153
+ const source = await readFile15(path, "utf8");
6154
+ const requirements = absoluteDeviceNativeRequirements(plan);
6155
+ const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
6156
+ const ownedStart = source.indexOf(START_MARKER2);
6157
+ const ownedEnd = source.indexOf(END_MARKER2);
6158
+ const ownsSystemBars = ownedStart >= 0 && ownedEnd > ownedStart && source.slice(ownedStart, ownedEnd).includes("UIViewControllerBasedStatusBarAppearance");
6159
+ if (requirements.iosSystemBars && existingSystemBars?.[1] === "false" && !ownsSystemBars)
6160
+ throw new TypeError("iOS system bars require UIViewControllerBasedStatusBarAppearance to be true.");
6161
+ const usageContent = requirements.iosUsageDescriptions.map((purpose) => ` <key>${IOS_KEYS[purpose]}</key>
6162
+ <string>${escapeXml2(iosDescription(config.appName, purpose))}</string>`).join(`
6163
+ `);
6164
+ const systemBarsContent = requirements.iosSystemBars && (existingSystemBars === null || ownsSystemBars) ? ` <key>UIViewControllerBasedStatusBarAppearance</key>
6165
+ <true/>` : "";
6166
+ const content = [usageContent, systemBarsContent].filter(Boolean).join(`
6167
+ `);
6168
+ const region = content ? ` ${START_MARKER2}
6169
+ ${content}
6170
+ ${END_MARKER2}
6171
+ ` : "";
6172
+ const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
6173
+ const privacyPath = join16(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
6174
+ const privacyCurrent = await optionalSource(privacyPath);
6175
+ const privacySource = privacyManifestSource(privacyCurrent, requirements);
6176
+ const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
6177
+ writeIosPrivacyManifest(privacyPath, privacyCurrent, privacySource),
6178
+ configureIosPrivacyProject(config, requirements),
6179
+ configureIosPushNotifications(config, requirements.iosPushNotifications)
6180
+ ]);
6181
+ return infoChanged || privacyChanged || projectChanged || pushChanged;
6182
+ };
6183
+ var configureIosPushNotifications = async (config, enabled) => {
6184
+ const entitlementsPath = join16(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
6185
+ const entitlements = await optionalSource(entitlementsPath);
6186
+ if (entitlements === null && !enabled)
6187
+ return false;
6188
+ if (entitlements === null)
6189
+ throw new TypeError("AbsoluteJS iOS entitlements are missing. Run native deep-link projection before device capabilities.");
6190
+ const entitlementRegion = enabled ? ` <!-- ${PUSH_START_MARKER} -->
6191
+ <key>aps-environment</key>
6192
+ <string>development</string>
6193
+ <!-- ${PUSH_END_MARKER} -->
6194
+ ` : "";
6195
+ const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
6196
+ const delegatePath = join16(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
6197
+ const delegate = await optionalSource(delegatePath);
6198
+ if (delegate === null && !enabled)
6199
+ return false;
6200
+ if (delegate === null)
6201
+ throw new TypeError("Capacitor AppDelegate.swift is missing for iOS push notifications.");
6202
+ const hasSuccess = delegate.includes("didRegisterForRemoteNotificationsWithDeviceToken");
6203
+ const hasFailure = delegate.includes("didFailToRegisterForRemoteNotificationsWithError");
6204
+ if (enabled && hasSuccess !== hasFailure && !delegate.includes(PUSH_START_MARKER))
6205
+ throw new TypeError("iOS AppDelegate has a partial custom remote-notification registration implementation.");
6206
+ const alreadyForwarded = enabled && hasSuccess && hasFailure && delegate.includes("capacitorDidRegisterForRemoteNotifications") && delegate.includes("capacitorDidFailToRegisterForRemoteNotifications");
6207
+ const swiftRegion = enabled && !alreadyForwarded ? `
6208
+ // ${PUSH_START_MARKER}
6209
+ func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
6210
+ NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)
6211
+ }
6212
+
6213
+ func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
6214
+ NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)
6215
+ }
6216
+ // ${PUSH_END_MARKER}
6217
+ ` : "";
6218
+ const nextDelegate = alreadyForwarded ? delegate : replacePushRegion(delegate, swiftRegion, delegate.lastIndexOf("}"));
6219
+ const changed = await Promise.all([
6220
+ writeChangedFile2(entitlementsPath, nextEntitlements),
6221
+ writeChangedFile2(delegatePath, nextDelegate)
6222
+ ]);
6223
+ return changed.some(Boolean);
6224
+ };
6225
+ var replacePushRegion = (source, region, insertion) => {
6226
+ const start = source.indexOf(PUSH_START_MARKER);
6227
+ const end = source.indexOf(PUSH_END_MARKER);
6228
+ if (start < 0 !== end < 0 || start >= 0 && end < start)
6229
+ throw new TypeError("AbsoluteJS push-notification ownership markers are malformed.");
6230
+ if (start >= 0) {
6231
+ const lineStart = source.lastIndexOf(`
6232
+ `, start) + 1;
6233
+ const nextLine = source.indexOf(`
6234
+ `, end + PUSH_END_MARKER.length);
6235
+ const lineEnd = nextLine < 0 ? source.length : nextLine + 1;
6236
+ return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
6237
+ }
6238
+ if (!region)
6239
+ return source;
6240
+ if (insertion < 0)
6241
+ throw new TypeError("Could not find a safe native project location for push notifications.");
6242
+ return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
6243
+ };
6244
+ var configureAndroid2 = async (config, plan) => {
6245
+ const path = join16(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
6246
+ const source = await readFile15(path, "utf8");
6247
+ const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
6248
+ const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
6249
+ `);
6250
+ const region = content ? ` ${START_MARKER2}
6251
+ ${content}
6252
+ ${END_MARKER2}
6253
+ ` : "";
6254
+ const application = source.indexOf("<application");
6255
+ const insertion = application === NOT_FOUND2 ? NOT_FOUND2 : source.lastIndexOf(`
6256
+ `, application) + 1;
6257
+ const nextManifest = managed(source, region, insertion);
6258
+ if (!plan.capabilities.includes("pushNotifications"))
6259
+ return writeChangedFile2(path, nextManifest);
6260
+ const firebaseSource = await optionalSource(config.pushAndroidGoogleServicesFile);
6261
+ if (firebaseSource === null)
6262
+ throw new TypeError(`Push notifications require Firebase config at ${config.pushAndroidGoogleServicesFile}. Set mobile.pushNotifications.android.googleServicesFile to override it.`);
6263
+ let firebase;
6264
+ try {
6265
+ firebase = JSON.parse(firebaseSource);
6266
+ } catch (error) {
6267
+ throw new TypeError("Android google-services.json is invalid JSON.", {
6268
+ cause: error
6269
+ });
6270
+ }
6271
+ const clients = typeof firebase === "object" && firebase !== null ? Reflect.get(firebase, "client") : undefined;
6272
+ const matchesApp = Array.isArray(clients) && clients.some((client) => {
6273
+ const info = typeof client === "object" && client !== null ? Reflect.get(client, "client_info") : undefined;
6274
+ const android = typeof info === "object" && info !== null ? Reflect.get(info, "android_client_info") : undefined;
6275
+ return typeof android === "object" && android !== null && Reflect.get(android, "package_name") === config.appId;
6276
+ });
6277
+ if (!matchesApp)
6278
+ throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
6279
+ const [manifestChanged, firebaseChanged] = await Promise.all([
6280
+ writeChangedFile2(path, nextManifest),
6281
+ writeOptionalChangedFile(join16(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
6282
+ ]);
6283
+ return manifestChanged || firebaseChanged;
6284
+ };
6285
+ var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
6286
+ const results = await Promise.all(platforms.map(async (platform) => ({
6287
+ didChange: platform === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
6288
+ platform
6289
+ })));
6290
+ return {
6291
+ changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
6292
+ };
6293
+ };
4108
6294
  // src/mobile/releasePublisher.ts
4109
6295
  import { access as access9 } from "fs/promises";
4110
- import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve11, sep as sep5 } from "path";
6296
+ import { isAbsolute as isAbsolute6, relative as relative10, resolve as resolve13, sep as sep6 } from "path";
4111
6297
  import { pathToFileURL as pathToFileURL3 } from "url";
4112
6298
  var prepareAbsoluteIosRelease = async (publisher, options) => {
4113
6299
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -4133,10 +6319,10 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
4133
6319
  var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4134
6320
  var isPublisher = (value) => isRecord8(value) && typeof value.publish === "function";
4135
6321
  var publisherModulePath = (projectRoot, requested) => {
4136
- const root = resolve11(projectRoot);
4137
- const path = resolve11(root, requested);
4138
- const projectRelative = relative7(root, path);
4139
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep5}`) || isAbsolute5(projectRelative)) {
6322
+ const root = resolve13(projectRoot);
6323
+ const path = resolve13(root, requested);
6324
+ const projectRelative = relative10(root, path);
6325
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
4140
6326
  throw new TypeError("mobile publish --registry must remain inside the project.");
4141
6327
  }
4142
6328
  return path;
@@ -4204,67 +6390,112 @@ var publishAbsoluteIosRelease = async (options) => {
4204
6390
  return publication;
4205
6391
  };
4206
6392
  // src/mobile/routeMetadataTransform.ts
4207
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
4208
- import { dirname as dirname7, extname as extname2, relative as relative8, resolve as resolve12 } from "path";
4209
- import ts from "typescript";
6393
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
6394
+ import { dirname as dirname10, extname as extname4, relative as relative11, resolve as resolve14 } from "path";
6395
+ import ts2 from "typescript";
4210
6396
  var ROUTE_METHODS = new Set(["get", "head"]);
4211
6397
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
4212
- var PAGE_HANDLER = "handleReactPageRequest";
6398
+ var PAGE_HANDLERS = new Map([
6399
+ [
6400
+ "handleHTMLPageRequest",
6401
+ { framework: "html", inputKind: "static", propsProperty: "props" }
6402
+ ],
6403
+ [
6404
+ "handleHTMXPageRequest",
6405
+ { framework: "htmx", inputKind: "static", propsProperty: "props" }
6406
+ ],
6407
+ [
6408
+ "handleAngularPageRequest",
6409
+ {
6410
+ bundleProperty: "indexPath",
6411
+ framework: "angular",
6412
+ propsProperty: "requestContext",
6413
+ sourceProperty: "pagePath"
6414
+ }
6415
+ ],
6416
+ [
6417
+ "handleReactPageRequest",
6418
+ {
6419
+ bundleProperty: "index",
6420
+ framework: "react",
6421
+ pageProperty: "Page",
6422
+ propsProperty: "props"
6423
+ }
6424
+ ],
6425
+ [
6426
+ "handleSveltePageRequest",
6427
+ {
6428
+ bundleProperty: "indexPath",
6429
+ framework: "svelte",
6430
+ propsProperty: "props",
6431
+ sourceProperty: "pagePath"
6432
+ }
6433
+ ],
6434
+ [
6435
+ "handleVuePageRequest",
6436
+ {
6437
+ bundleProperty: "indexPath",
6438
+ framework: "vue",
6439
+ propsProperty: "props",
6440
+ sourceProperty: "pagePath"
6441
+ }
6442
+ ]
6443
+ ]);
4213
6444
  var posixPath = (value) => value.replace(/\\/g, "/");
4214
- var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname7(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
6445
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname10(entry), existsSync3, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
4215
6446
  var createProgram = (entry, projectRoot) => {
4216
6447
  const configPath = findTsconfig(entry, projectRoot);
4217
6448
  if (!configPath) {
4218
- return ts.createProgram([entry], {
6449
+ return ts2.createProgram([entry], {
4219
6450
  allowJs: true,
4220
- jsx: ts.JsxEmit.ReactJSX,
4221
- module: ts.ModuleKind.ESNext,
4222
- moduleResolution: ts.ModuleResolutionKind.Bundler,
4223
- target: ts.ScriptTarget.ESNext
6451
+ jsx: ts2.JsxEmit.ReactJSX,
6452
+ module: ts2.ModuleKind.ESNext,
6453
+ moduleResolution: ts2.ModuleResolutionKind.Bundler,
6454
+ target: ts2.ScriptTarget.ESNext
4224
6455
  });
4225
6456
  }
4226
- const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname7(configPath));
6457
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath, (path) => readFileSync5(path, "utf8")).config, ts2.sys, dirname10(configPath));
4227
6458
  if (!parsed.fileNames.includes(entry))
4228
6459
  parsed.fileNames.push(entry);
4229
- return ts.createProgram(parsed.fileNames, parsed.options);
6460
+ return ts2.createProgram(parsed.fileNames, parsed.options);
4230
6461
  };
4231
6462
  var propertyName = (property) => {
4232
6463
  if (!("name" in property) || !property.name)
4233
6464
  return;
4234
- if (ts.isIdentifier(property.name))
6465
+ if (ts2.isIdentifier(property.name))
4235
6466
  return property.name.text;
4236
- if (ts.isStringLiteralLike(property.name))
6467
+ if (ts2.isStringLiteralLike(property.name))
4237
6468
  return property.name.text;
4238
6469
  return;
4239
6470
  };
4240
- var objectPropertyExpression = (object, name) => {
4241
- const property = object.properties.find((candidate) => propertyName(candidate) === name);
4242
- if (property && ts.isPropertyAssignment(property)) {
6471
+ var objectPropertyExpression = (object3, name) => {
6472
+ const property = object3.properties.find((candidate) => propertyName(candidate) === name);
6473
+ if (property && ts2.isPropertyAssignment(property)) {
4243
6474
  return property.initializer;
4244
6475
  }
4245
- if (property && ts.isShorthandPropertyAssignment(property)) {
6476
+ if (property && ts2.isShorthandPropertyAssignment(property)) {
4246
6477
  return property.name;
4247
6478
  }
4248
6479
  return;
4249
6480
  };
4250
6481
  var serializeType = (type, checker, ancestors = new Set) => {
4251
- if (type.flags & ts.TypeFlags.Any)
6482
+ if (type.flags & ts2.TypeFlags.Any)
4252
6483
  return { type: "any" };
4253
- if (type.flags & ts.TypeFlags.Unknown)
6484
+ if (type.flags & ts2.TypeFlags.Unknown)
4254
6485
  return { type: "unknown" };
4255
- if (type.flags & ts.TypeFlags.Never)
6486
+ if (type.flags & ts2.TypeFlags.Never)
4256
6487
  return { type: "never" };
4257
- if (type.flags & ts.TypeFlags.StringLike)
6488
+ if (type.flags & ts2.TypeFlags.StringLike)
4258
6489
  return { type: "string" };
4259
- if (type.flags & ts.TypeFlags.NumberLike)
6490
+ if (type.flags & ts2.TypeFlags.NumberLike)
4260
6491
  return { type: "number" };
4261
- if (type.flags & ts.TypeFlags.BooleanLike)
6492
+ if (type.flags & ts2.TypeFlags.BooleanLike)
4262
6493
  return { type: "boolean" };
4263
- if (type.flags & ts.TypeFlags.BigIntLike)
6494
+ if (type.flags & ts2.TypeFlags.BigIntLike)
4264
6495
  return { type: "bigint" };
4265
- if (type.flags & ts.TypeFlags.Null)
6496
+ if (type.flags & ts2.TypeFlags.Null)
4266
6497
  return { type: "null" };
4267
- if (type.flags & ts.TypeFlags.Undefined)
6498
+ if (type.flags & ts2.TypeFlags.Undefined)
4268
6499
  return { type: "undefined" };
4269
6500
  if (type.isUnion()) {
4270
6501
  return {
@@ -4278,11 +6509,11 @@ var serializeType = (type, checker, ancestors = new Set) => {
4278
6509
  }
4279
6510
  if (ancestors.has(type)) {
4280
6511
  return {
4281
- ref: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
6512
+ ref: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
4282
6513
  };
4283
6514
  }
4284
6515
  ancestors.add(type);
4285
- const arrayElement = checker.getIndexTypeOfType(type, ts.IndexKind.Number);
6516
+ const arrayElement = checker.getIndexTypeOfType(type, ts2.IndexKind.Number);
4286
6517
  const properties = checker.getPropertiesOfType(type);
4287
6518
  let schema;
4288
6519
  if (arrayElement && properties.some(({ name }) => name === "length")) {
@@ -4297,7 +6528,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
4297
6528
  return [
4298
6529
  property.name,
4299
6530
  {
4300
- optional: Boolean(property.flags & ts.SymbolFlags.Optional),
6531
+ optional: Boolean(property.flags & ts2.SymbolFlags.Optional),
4301
6532
  schema: serializeType(propertyType, checker, ancestors)
4302
6533
  }
4303
6534
  ];
@@ -4305,7 +6536,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
4305
6536
  schema = { properties: Object.fromEntries(entries), type: "object" };
4306
6537
  } else {
4307
6538
  schema = {
4308
- type: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
6539
+ type: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
4309
6540
  };
4310
6541
  }
4311
6542
  ancestors.delete(type);
@@ -4323,25 +6554,25 @@ var pagePropsType = (pageExpression, propsExpression, checker) => {
4323
6554
  };
4324
6555
  var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
4325
6556
  let symbol = checker.getSymbolAtLocation(expression);
4326
- if (symbol?.flags && symbol.flags & ts.SymbolFlags.Alias) {
6557
+ if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) {
4327
6558
  symbol = checker.getAliasedSymbol(symbol);
4328
6559
  }
4329
6560
  const declaration = symbol?.declarations?.[0];
4330
6561
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
4331
6562
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
4332
- const source = posixPath(relative8(projectRoot, file));
6563
+ const source = posixPath(relative11(projectRoot, file));
4333
6564
  return `${source}#${exportedName}`;
4334
6565
  };
4335
6566
  var resolveAlias = (symbol, checker) => {
4336
- if (!(symbol.flags & ts.SymbolFlags.Alias))
6567
+ if (!(symbol.flags & ts2.SymbolFlags.Alias))
4337
6568
  return symbol;
4338
6569
  return checker.getAliasedSymbol(symbol);
4339
6570
  };
4340
6571
  var assetKey = (expression, checker, seen = new Set) => {
4341
6572
  if (!expression)
4342
6573
  return;
4343
- if (ts.isIdentifier(expression)) {
4344
- const unresolved = ts.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
6574
+ if (ts2.isIdentifier(expression)) {
6575
+ const unresolved = ts2.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
4345
6576
  if (!unresolved)
4346
6577
  return;
4347
6578
  const symbol = resolveAlias(unresolved, checker);
@@ -4349,28 +6580,123 @@ var assetKey = (expression, checker, seen = new Set) => {
4349
6580
  return;
4350
6581
  seen.add(symbol);
4351
6582
  const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
4352
- if (!declaration || !ts.isVariableDeclaration(declaration))
6583
+ if (!declaration || !ts2.isVariableDeclaration(declaration))
4353
6584
  return;
4354
6585
  return assetKey(declaration.initializer, checker, seen);
4355
6586
  }
4356
- if (!ts.isCallExpression(expression))
6587
+ if (!ts2.isCallExpression(expression))
4357
6588
  return;
4358
- if (!ts.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
6589
+ if (!ts2.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
4359
6590
  return;
4360
6591
  }
4361
6592
  const [, key] = expression.arguments;
4362
- return key && ts.isStringLiteralLike(key) ? key.text : undefined;
6593
+ return key && ts2.isStringLiteralLike(key) ? key.text : undefined;
6594
+ };
6595
+ var staticString = (expression, bindings) => {
6596
+ if (ts2.isStringLiteralLike(expression))
6597
+ return expression.text;
6598
+ if (ts2.isIdentifier(expression))
6599
+ return bindings.get(expression.text);
6600
+ if (ts2.isNoSubstitutionTemplateLiteral(expression))
6601
+ return expression.text;
6602
+ if (!ts2.isTemplateExpression(expression))
6603
+ return;
6604
+ let value = expression.head.text;
6605
+ for (const span of expression.templateSpans) {
6606
+ const substitution = staticString(span.expression, bindings);
6607
+ if (substitution === undefined)
6608
+ return;
6609
+ value += substitution + span.literal.text;
6610
+ }
6611
+ return value;
6612
+ };
6613
+ var assetKeyWithBindings = (expression, checker, bindings = new Map) => {
6614
+ if (!expression)
6615
+ return;
6616
+ if (ts2.isCallExpression(expression) && ts2.isIdentifier(expression.expression) && expression.expression.text === "asset") {
6617
+ const [, key] = expression.arguments;
6618
+ return key ? staticString(key, bindings) : undefined;
6619
+ }
6620
+ return assetKey(expression, checker);
6621
+ };
6622
+ var callableObject = (call, checker) => {
6623
+ const symbol = checker.getSymbolAtLocation(call.expression);
6624
+ const resolved = symbol ? resolveAlias(symbol, checker) : undefined;
6625
+ const declaration = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
6626
+ let callable;
6627
+ if (declaration && ts2.isFunctionDeclaration(declaration)) {
6628
+ callable = declaration;
6629
+ } else if (declaration && ts2.isVariableDeclaration(declaration) && declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer))) {
6630
+ callable = declaration.initializer;
6631
+ }
6632
+ if (!callable)
6633
+ return;
6634
+ const bindings = new Map;
6635
+ callable.parameters.forEach((parameter, index) => {
6636
+ if (!ts2.isIdentifier(parameter.name))
6637
+ return;
6638
+ const argument = call.arguments[index];
6639
+ if (!argument)
6640
+ return;
6641
+ const value = staticString(argument, new Map);
6642
+ if (value !== undefined)
6643
+ bindings.set(parameter.name.text, value);
6644
+ });
6645
+ const { body } = callable;
6646
+ if (!body)
6647
+ return;
6648
+ const expressionBody = ts2.isParenthesizedExpression(body) ? body.expression : body;
6649
+ if (ts2.isObjectLiteralExpression(expressionBody)) {
6650
+ return { bindings, object: expressionBody };
6651
+ }
6652
+ if (ts2.isBlock(body)) {
6653
+ const returned = body.statements.find(ts2.isReturnStatement)?.expression;
6654
+ if (returned && ts2.isObjectLiteralExpression(returned)) {
6655
+ return { bindings, object: returned };
6656
+ }
6657
+ }
6658
+ return;
6659
+ };
6660
+ var spreadObject = (expression, checker, bindings) => {
6661
+ if (ts2.isObjectLiteralExpression(expression)) {
6662
+ return { bindings, object: expression };
6663
+ }
6664
+ if (!ts2.isCallExpression(expression))
6665
+ return;
6666
+ return callableObject(expression, checker);
6667
+ };
6668
+ var objectAssetKey = (object3, name, checker, bindings = new Map) => {
6669
+ for (const property of [...object3.properties].reverse()) {
6670
+ if (propertyName(property) === name && ts2.isShorthandPropertyAssignment(property)) {
6671
+ return assetKeyWithBindings(property.name, checker, bindings);
6672
+ }
6673
+ if (propertyName(property) === name && ts2.isPropertyAssignment(property)) {
6674
+ return assetKeyWithBindings(property.initializer, checker, bindings);
6675
+ }
6676
+ if (!ts2.isSpreadAssignment(property))
6677
+ continue;
6678
+ const nestedObject = spreadObject(property.expression, checker, bindings);
6679
+ if (!nestedObject)
6680
+ continue;
6681
+ const nested = objectAssetKey(nestedObject.object, name, checker, nestedObject.bindings);
6682
+ if (nested)
6683
+ return nested;
6684
+ }
6685
+ return;
4363
6686
  };
4364
6687
  var findPageCall = (nodes) => {
4365
6688
  let found;
4366
6689
  const visit = (candidate) => {
4367
6690
  if (found)
4368
6691
  return;
4369
- if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && candidate.expression.text === PAGE_HANDLER) {
4370
- found = candidate;
6692
+ if (ts2.isCallExpression(candidate) && ts2.isIdentifier(candidate.expression) && PAGE_HANDLERS.has(candidate.expression.text)) {
6693
+ const definition = PAGE_HANDLERS.get(candidate.expression.text);
6694
+ if (!definition)
6695
+ return;
6696
+ found = { definition, node: candidate };
4371
6697
  return;
4372
6698
  }
4373
- ts.forEachChild(candidate, visit);
6699
+ ts2.forEachChild(candidate, visit);
4374
6700
  };
4375
6701
  for (const node of nodes)
4376
6702
  visit(node);
@@ -4379,37 +6705,74 @@ var findPageCall = (nodes) => {
4379
6705
  var isProjectSource = (sourceFile, resolvedFile, projectRoot) => !sourceFile.isDeclarationFile && !resolvedFile.includes("/node_modules/") && resolvedFile.startsWith(`${projectRoot}/`);
4380
6706
  var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
4381
6707
  const callee = node.expression;
4382
- if (!ts.isPropertyAccessExpression(callee))
6708
+ if (!ts2.isPropertyAccessExpression(callee))
4383
6709
  return;
4384
6710
  if (!ROUTE_METHODS.has(callee.name.text))
4385
6711
  return;
4386
6712
  const [routePath] = node.arguments;
4387
- if (!routePath || !ts.isStringLiteralLike(routePath))
6713
+ if (!routePath || !ts2.isStringLiteralLike(routePath))
4388
6714
  return;
4389
- const pageCall = findPageCall(node.arguments.slice(1));
6715
+ const foundPageCall = findPageCall(node.arguments.slice(1));
6716
+ const pageCall = foundPageCall?.node;
6717
+ const definition = foundPageCall?.definition;
4390
6718
  const [input] = pageCall?.arguments ?? [];
4391
- if (!pageCall || !input || !ts.isObjectLiteralExpression(input)) {
6719
+ if (!pageCall || !input) {
4392
6720
  return;
4393
6721
  }
4394
- const page = objectPropertyExpression(input, "Page");
4395
- if (!page)
6722
+ if (!definition)
6723
+ return;
6724
+ if (definition.inputKind === "static") {
6725
+ const bundleKey2 = assetKey(input, checker);
6726
+ if (!bundleKey2)
6727
+ return;
6728
+ const pageId2 = `${definition.framework}:${bundleKey2}`;
6729
+ const propsSchemaHash2 = hashAbsoluteMobilePropsSchema({
6730
+ properties: {},
6731
+ type: "object"
6732
+ });
6733
+ return {
6734
+ inputKind: "static",
6735
+ metadata: {
6736
+ bundleKey: bundleKey2,
6737
+ contract: `${definition.framework}:${pageId2}:${propsSchemaHash2}`,
6738
+ framework: definition.framework,
6739
+ pageId: pageId2,
6740
+ propsSchemaHash: propsSchemaHash2
6741
+ },
6742
+ pageCallStart: pageCall.getStart(sourceFile),
6743
+ routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
6744
+ };
6745
+ }
6746
+ if (!ts2.isObjectLiteralExpression(input) || !definition.bundleProperty) {
4396
6747
  return;
4397
- const props = objectPropertyExpression(input, "props");
4398
- const index = objectPropertyExpression(input, "index");
4399
- const bundleKey = assetKey(index, checker);
6748
+ }
6749
+ const page = definition.pageProperty ? objectPropertyExpression(input, definition.pageProperty) : undefined;
6750
+ const source = definition.sourceProperty ? objectAssetKey(input, definition.sourceProperty, checker) : undefined;
6751
+ if (definition.pageProperty && !page)
6752
+ return;
6753
+ if (definition.sourceProperty && !source)
6754
+ return;
6755
+ const props = objectPropertyExpression(input, definition.propsProperty);
6756
+ const bundleKey = objectAssetKey(input, definition.bundleProperty, checker);
4400
6757
  if (!bundleKey)
4401
6758
  return;
4402
- const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot);
4403
- const schema = serializeType(pagePropsType(page, props, checker), checker);
6759
+ const pageId = page ? resolvePageIdentity(page, sourceFile, checker, projectRoot) : `${definition.framework}:${source}`;
6760
+ let propsType;
6761
+ if (page)
6762
+ propsType = pagePropsType(page, props, checker);
6763
+ else if (props)
6764
+ propsType = checker.getTypeAtLocation(props);
6765
+ const schema = propsType ? serializeType(propsType, checker) : { properties: {}, type: "object" };
4404
6766
  const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
4405
6767
  const metadata = {
4406
6768
  bundleKey,
4407
- contract: `react:${pageId}:${propsSchemaHash}`,
4408
- framework: "react",
6769
+ contract: `${definition.framework}:${pageId}:${propsSchemaHash}`,
6770
+ framework: definition.framework,
4409
6771
  pageId,
4410
6772
  propsSchemaHash
4411
6773
  };
4412
6774
  const result = {
6775
+ inputKind: "object",
4413
6776
  metadata,
4414
6777
  pageCallStart: pageCall.getStart(sourceFile),
4415
6778
  routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
@@ -4422,21 +6785,21 @@ var analyzeSourceFile = (sourceFile, checker, projectRoot) => {
4422
6785
  byRouteCall: new Map
4423
6786
  };
4424
6787
  const visit = (node) => {
4425
- const result = ts.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
6788
+ const result = ts2.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
4426
6789
  if (result) {
4427
6790
  analysis.byPageCall.set(result.pageCallStart, result);
4428
6791
  analysis.byRouteCall.set(result.routeCallSpan, result);
4429
6792
  }
4430
- ts.forEachChild(node, visit);
6793
+ ts2.forEachChild(node, visit);
4431
6794
  };
4432
- ts.forEachChild(sourceFile, visit);
6795
+ ts2.forEachChild(sourceFile, visit);
4433
6796
  return analysis;
4434
6797
  };
4435
6798
  var analyzeProgram = (program, projectRoot) => {
4436
6799
  const checker = program.getTypeChecker();
4437
6800
  const analyzed = new Map;
4438
6801
  for (const sourceFile of program.getSourceFiles()) {
4439
- const resolvedFile = resolve12(sourceFile.fileName);
6802
+ const resolvedFile = resolve14(sourceFile.fileName);
4440
6803
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
4441
6804
  continue;
4442
6805
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -4445,34 +6808,44 @@ var analyzeProgram = (program, projectRoot) => {
4445
6808
  }
4446
6809
  return analyzed;
4447
6810
  };
4448
- var metadataExpression = (metadata) => ts.factory.createObjectLiteralExpression(Object.entries(metadata).map(([key, item]) => ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(key), ts.factory.createStringLiteral(item))), false);
6811
+ var metadataExpression = (metadata) => ts2.factory.createObjectLiteralExpression(Object.entries(metadata).map(([key, item]) => ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(key), ts2.factory.createStringLiteral(item))), false);
4449
6812
  var routeOptions = (existing, metadata) => {
4450
- const detail = ts.factory.createObjectLiteralExpression([
4451
- ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
6813
+ const detail = ts2.factory.createObjectLiteralExpression([
6814
+ ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
4452
6815
  ]);
4453
6816
  if (!existing) {
4454
- return ts.factory.createObjectLiteralExpression([
4455
- ts.factory.createPropertyAssignment("detail", detail)
6817
+ return ts2.factory.createObjectLiteralExpression([
6818
+ ts2.factory.createPropertyAssignment("detail", detail)
4456
6819
  ]);
4457
6820
  }
4458
- return ts.factory.createObjectLiteralExpression([
4459
- ts.factory.createSpreadAssignment(existing),
4460
- ts.factory.createPropertyAssignment("detail", ts.factory.createObjectLiteralExpression([
4461
- ts.factory.createSpreadAssignment(ts.factory.createPropertyAccessExpression(existing, "detail")),
4462
- ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
6821
+ return ts2.factory.createObjectLiteralExpression([
6822
+ ts2.factory.createSpreadAssignment(existing),
6823
+ ts2.factory.createPropertyAssignment("detail", ts2.factory.createObjectLiteralExpression([
6824
+ ts2.factory.createSpreadAssignment(ts2.factory.createPropertyAccessExpression(existing, "detail")),
6825
+ ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
4463
6826
  ]))
4464
6827
  ]);
4465
6828
  };
4466
6829
  var transformPageCall = (node, page) => {
4467
6830
  if (!page)
4468
6831
  return;
6832
+ if (page.inputKind === "static") {
6833
+ const [pagePath, existingOptions, ...rest] = node.arguments;
6834
+ if (!pagePath)
6835
+ return;
6836
+ const options = ts2.factory.createObjectLiteralExpression([
6837
+ ...existingOptions ? [ts2.factory.createSpreadAssignment(existingOptions)] : [],
6838
+ ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
6839
+ ]);
6840
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [pagePath, options, ...rest]);
6841
+ }
4469
6842
  const [input] = node.arguments;
4470
- if (!input || !ts.isObjectLiteralExpression(input))
6843
+ if (!input || !ts2.isObjectLiteralExpression(input))
4471
6844
  return;
4472
- return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
4473
- ts.factory.updateObjectLiteralExpression(input, [
6845
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [
6846
+ ts2.factory.updateObjectLiteralExpression(input, [
4474
6847
  ...input.properties,
4475
- ts.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
6848
+ ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
4476
6849
  ]),
4477
6850
  ...node.arguments.slice(1)
4478
6851
  ]);
@@ -4485,16 +6858,16 @@ var transformRouteCall = (node, route) => {
4485
6858
  return;
4486
6859
  const options = maybeHandler ? maybeOptions : undefined;
4487
6860
  const handler = maybeHandler ?? maybeOptions;
4488
- return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
6861
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
4489
6862
  };
4490
6863
  var transformFile = (source, fileName, analysis) => {
4491
- const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
6864
+ const sourceFile = ts2.createSourceFile(fileName, source, ts2.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS);
4492
6865
  const transformer = (context) => {
4493
6866
  const visit = (node) => {
4494
- if (!ts.isCallExpression(node)) {
4495
- return ts.visitEachChild(node, visit, context);
6867
+ if (!ts2.isCallExpression(node)) {
6868
+ return ts2.visitEachChild(node, visit, context);
4496
6869
  }
4497
- const transformedChildren = ts.visitEachChild(node, visit, context);
6870
+ const transformedChildren = ts2.visitEachChild(node, visit, context);
4498
6871
  const page = analysis.byPageCall.get(node.getStart(sourceFile));
4499
6872
  const transformedPage = transformPageCall(transformedChildren, page);
4500
6873
  if (transformedPage)
@@ -4505,64 +6878,280 @@ var transformFile = (source, fileName, analysis) => {
4505
6878
  return transformedRoute;
4506
6879
  return transformedChildren;
4507
6880
  };
4508
- return (node) => ts.visitNode(node, visit, ts.isSourceFile) ?? node;
6881
+ return (node) => ts2.visitNode(node, visit, ts2.isSourceFile) ?? node;
4509
6882
  };
4510
- const result = ts.transform(sourceFile, [transformer]);
6883
+ const result = ts2.transform(sourceFile, [transformer]);
4511
6884
  try {
4512
6885
  const [transformed] = result.transformed;
4513
6886
  if (!transformed)
4514
6887
  throw new TypeError("Mobile route transform failed.");
4515
- return ts.createPrinter().printFile(transformed);
6888
+ return ts2.createPrinter().printFile(transformed);
4516
6889
  } finally {
4517
6890
  result.dispose();
4518
6891
  }
4519
6892
  };
4520
6893
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
4521
6894
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
4522
- const projectRoot = resolve12(options.projectRoot ?? process.cwd());
4523
- const entry = resolve12(options.entry);
6895
+ const projectRoot = resolve14(options.projectRoot ?? process.cwd());
6896
+ const entry = resolve14(options.entry);
4524
6897
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
4525
6898
  return {
4526
6899
  name: "absolute-mobile-route-metadata",
4527
6900
  setup(build) {
4528
6901
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
4529
- const analysis = analyzed.get(resolve12(path));
6902
+ const analysis = analyzed.get(resolve14(path));
4530
6903
  if (!analysis)
4531
6904
  return;
4532
6905
  const source = await Bun.file(path).text();
4533
6906
  return {
4534
6907
  contents: transformFile(source, path, analysis),
4535
- loader: extname2(path).endsWith("x") ? "tsx" : "ts"
6908
+ loader: extname4(path).endsWith("x") ? "tsx" : "ts"
4536
6909
  };
4537
6910
  });
4538
6911
  }
4539
6912
  };
4540
6913
  };
4541
6914
  var inspectAbsoluteMobileRouteMetadata = (options) => {
4542
- const projectRoot = resolve12(options.projectRoot ?? process.cwd());
4543
- const entry = resolve12(options.entry);
6915
+ const projectRoot = resolve14(options.projectRoot ?? process.cwd());
6916
+ const entry = resolve14(options.entry);
4544
6917
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
4545
6918
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
4546
- file: posixPath(relative8(projectRoot, file)),
6919
+ file: posixPath(relative11(projectRoot, file)),
4547
6920
  metadata
4548
6921
  })));
4549
6922
  };
6923
+ // node_modules/@absolutejs/http/dist/index.js
6924
+ class AbsoluteHttpError extends Error {
6925
+ code;
6926
+ cause;
6927
+ status;
6928
+ url;
6929
+ constructor(code, message, options = {}) {
6930
+ super(message);
6931
+ this.name = "AbsoluteHttpError";
6932
+ this.code = code;
6933
+ this.cause = options.cause;
6934
+ this.status = options.status;
6935
+ this.url = options.url;
6936
+ }
6937
+ }
6938
+ var loopbackHosts = new Set(["127.0.0.1", "[::1]", "localhost"]);
6939
+ var normalizeAbsoluteHttpOrigin = (value) => {
6940
+ let url;
6941
+ try {
6942
+ url = new URL(value);
6943
+ } catch (cause) {
6944
+ throw new AbsoluteHttpError("origin", "HTTP origin is invalid.", {
6945
+ cause
6946
+ });
6947
+ }
6948
+ const loopback = url.protocol === "http:" && loopbackHosts.has(url.hostname);
6949
+ if (url.protocol !== "https:" && !loopback)
6950
+ throw new AbsoluteHttpError("origin", "HTTP origin must use HTTPS outside loopback development.");
6951
+ if (url.username || url.password || url.pathname !== "/" || url.search || url.hash)
6952
+ throw new AbsoluteHttpError("origin", "HTTP origin cannot contain credentials, a path, query, or fragment.");
6953
+ return url.origin;
6954
+ };
6955
+ var resolveAbsoluteHttpUrl = (target, origin) => {
6956
+ let url;
6957
+ try {
6958
+ url = new URL(String(target), `${origin}/`);
6959
+ } catch (cause) {
6960
+ throw new AbsoluteHttpError("origin", "HTTP request URL is invalid.", {
6961
+ cause
6962
+ });
6963
+ }
6964
+ if (url.username || url.password)
6965
+ throw new AbsoluteHttpError("origin", "HTTP request URLs cannot contain credentials.");
6966
+ if (url.origin !== origin)
6967
+ throw new AbsoluteHttpError("origin", "Absolute HTTP refused a request outside the configured application origin.", { url: url.href });
6968
+ url.hash = "";
6969
+ return url;
6970
+ };
6971
+ var createAbsoluteHttpTransport = ({
6972
+ fetch: fetchImpl = globalThis.fetch,
6973
+ origin,
6974
+ runtime
6975
+ }) => ({
6976
+ fetch: fetchImpl,
6977
+ origin: normalizeAbsoluteHttpOrigin(origin),
6978
+ runtime
6979
+ });
6980
+ var createWebHttpTransport = (options = {}) => {
6981
+ const origin = options.origin ?? globalThis.location?.origin;
6982
+ if (!origin)
6983
+ throw new TypeError("The web HTTP transport requires a browser origin or explicit origin.");
6984
+ return createAbsoluteHttpTransport({
6985
+ ...options.fetch ? { fetch: options.fetch } : {},
6986
+ origin,
6987
+ runtime: "web"
6988
+ });
6989
+ };
6990
+ var RUNTIME_REGISTRY = Symbol.for("@absolutejs/http/runtime");
6991
+ var registryHost = globalThis;
6992
+ var isRuntimeRegistry = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
6993
+ var runtimeRegistry = (() => {
6994
+ const existing = registryHost[RUNTIME_REGISTRY];
6995
+ if (isRuntimeRegistry(existing))
6996
+ return existing;
6997
+ const created = { installations: [] };
6998
+ Object.defineProperty(registryHost, RUNTIME_REGISTRY, {
6999
+ configurable: false,
7000
+ enumerable: false,
7001
+ value: created,
7002
+ writable: false
7003
+ });
7004
+ return created;
7005
+ })();
7006
+ var fallbackTransport = () => {
7007
+ if (typeof location !== "undefined" && location.origin)
7008
+ return createWebHttpTransport();
7009
+ throw new AbsoluteHttpError("not-configured", "Absolute HTTP requires a request-scoped SSR transport outside a browser.");
7010
+ };
7011
+ var getAbsoluteHttpTransport = () => runtimeRegistry.installations.at(-1)?.transport ?? (runtimeRegistry.fallback ??= fallbackTransport());
7012
+ var installAbsoluteHttpTransport = (transport) => {
7013
+ const installation = { transport };
7014
+ runtimeRegistry.installations.push(installation);
7015
+ return () => {
7016
+ const index = runtimeRegistry.installations.indexOf(installation);
7017
+ if (index >= 0)
7018
+ runtimeRegistry.installations.splice(index, 1);
7019
+ };
7020
+ };
7021
+ var forbiddenCredentialHeaders = new Set([
7022
+ "authorization",
7023
+ "cookie",
7024
+ "proxy-authorization"
7025
+ ]);
7026
+ var transportFor = (configured) => configured ?? getAbsoluteHttpTransport();
7027
+ var safeHeaders = (input) => {
7028
+ const headers = new Headers(input);
7029
+ for (const name of forbiddenCredentialHeaders) {
7030
+ if (headers.has(name))
7031
+ throw new AbsoluteHttpError("origin", `Absolute HTTP owns the ${name} credential header for trusted transports.`);
7032
+ }
7033
+ return headers;
7034
+ };
7035
+ var requestFor = (target, options, transport) => {
7036
+ const source = target instanceof Request ? target : undefined;
7037
+ const requestedUrl = source ? source.url : target;
7038
+ const url = resolveAbsoluteHttpUrl(requestedUrl, transport.origin);
7039
+ const headers = safeHeaders(options?.headers ?? source?.headers);
7040
+ return new Request(source ?? url, {
7041
+ ...options,
7042
+ credentials: transport.runtime === "web" ? "same-origin" : "omit",
7043
+ headers,
7044
+ redirect: "error"
7045
+ });
7046
+ };
7047
+ var normalizeFetchError = (caught, url) => {
7048
+ if (caught instanceof AbsoluteHttpError)
7049
+ return caught;
7050
+ if (caught instanceof DOMException && (caught.name === "AbortError" || caught.name === "TimeoutError"))
7051
+ return new AbsoluteHttpError("aborted", "HTTP request was aborted.", {
7052
+ cause: caught,
7053
+ url
7054
+ });
7055
+ return new AbsoluteHttpError("network", "HTTP request failed.", {
7056
+ cause: caught,
7057
+ url
7058
+ });
7059
+ };
7060
+ var requireOk = (response) => {
7061
+ if (!response.ok)
7062
+ throw new AbsoluteHttpError("http", `HTTP request failed with status ${response.status}.`, { status: response.status, url: response.url });
7063
+ return response;
7064
+ };
7065
+ var parseJson2 = async (response) => {
7066
+ const text2 = await response.text();
7067
+ if (text2 === "")
7068
+ return;
7069
+ try {
7070
+ return JSON.parse(text2);
7071
+ } catch (cause) {
7072
+ throw new AbsoluteHttpError("response", "HTTP response is not valid JSON.", {
7073
+ cause,
7074
+ status: response.status,
7075
+ url: response.url
7076
+ });
7077
+ }
7078
+ };
7079
+ var jsonOptions = (method, body, options) => {
7080
+ const headers = new Headers(options?.headers);
7081
+ if (body !== undefined && !headers.has("content-type"))
7082
+ headers.set("content-type", "application/json");
7083
+ let encoded;
7084
+ try {
7085
+ encoded = body === undefined ? undefined : JSON.stringify(body);
7086
+ } catch (cause) {
7087
+ throw new AbsoluteHttpError("body", "HTTP body is not JSON serializable.", {
7088
+ cause
7089
+ });
7090
+ }
7091
+ return encoded === undefined ? { ...options, headers, method } : { ...options, body: encoded, headers, method };
7092
+ };
7093
+ var createAbsoluteHttpClient = (options = {}) => {
7094
+ const fetchTrusted = async (input, init) => {
7095
+ const transport = transportFor(options.transport);
7096
+ const request2 = requestFor(input, init, transport);
7097
+ try {
7098
+ return await transport.fetch(request2);
7099
+ } catch (caught) {
7100
+ throw normalizeFetchError(caught, request2.url);
7101
+ }
7102
+ };
7103
+ const request = (target, requestOptions) => fetchTrusted(target, requestOptions);
7104
+ const json = async (target, requestOptions) => parseJson2(requireOk(await request(target, requestOptions)));
7105
+ const jsonMethod = (method, target, body, requestOptions) => json(target, jsonOptions(method, body, requestOptions));
7106
+ return {
7107
+ delete: (target, requestOptions) => {
7108
+ const { body, ...optionsWithoutBody } = requestOptions ?? {};
7109
+ return jsonMethod("DELETE", target, body, optionsWithoutBody);
7110
+ },
7111
+ fetch: fetchTrusted,
7112
+ get: (target, requestOptions) => json(target, { ...requestOptions, method: "GET" }),
7113
+ json,
7114
+ origin: () => transportFor(options.transport).origin,
7115
+ patch: (target, body, requestOptions) => jsonMethod("PATCH", target, body, requestOptions),
7116
+ post: (target, body, requestOptions) => jsonMethod("POST", target, body, requestOptions),
7117
+ put: (target, body, requestOptions) => jsonMethod("PUT", target, body, requestOptions),
7118
+ request,
7119
+ text: async (target, requestOptions) => requireOk(await request(target, requestOptions)).text()
7120
+ };
7121
+ };
7122
+ var http = createAbsoluteHttpClient();
7123
+
7124
+ // src/mobile/shellHttp.ts
7125
+ var installAbsoluteMobileShellHttp = (origin, fetch2 = globalThis.fetch) => installAbsoluteHttpTransport(createAbsoluteHttpTransport({ fetch: fetch2, origin, runtime: "native" }));
4550
7126
  export {
4551
7127
  writeAbsoluteCapacitorConfig,
4552
7128
  waitForAbsoluteIosHmrLog,
4553
7129
  verifyAbsoluteMobileCompatibilityProducer,
4554
7130
  verifyAbsoluteMobileAssociationFiles,
7131
+ validateAbsoluteSshDestination,
7132
+ validateAbsoluteRemoteMacProfileName,
7133
+ syncAbsoluteRemoteMacProject,
7134
+ startAbsoluteRemoteIosDevSession,
4555
7135
  startAbsoluteIosDevSession,
7136
+ serializeAbsoluteMobileAuthEnvironment,
4556
7137
  runWithAbsoluteMobileProducer,
7138
+ runAbsoluteAndroidUpgradeConformance,
4557
7139
  retainAbsoluteMobileCompatibilityArtifacts,
4558
7140
  resolveAbsoluteMobileRoute,
7141
+ resolveAbsoluteMobileNavigation,
4559
7142
  resolveAbsoluteMobileDeepLink,
4560
7143
  resolveAbsoluteMobileCompatibilityRelease,
7144
+ resolveAbsoluteMobileAuthManifest,
7145
+ resolveAbsoluteDeviceCapabilityPlan,
4561
7146
  repairAbsoluteIosDevSession,
7147
+ removeAbsoluteRemoteMacProfile,
4562
7148
  redactAbsoluteIosLog,
4563
7149
  readAbsoluteMobileMaterializedReleases,
4564
7150
  publishAbsoluteIosRelease,
4565
7151
  publishAbsoluteAndroidRelease,
7152
+ projectUsesAbsoluteSync,
7153
+ projectUsesAbsoluteAuth,
7154
+ projectImportsAbsoluteDeviceCapability,
4566
7155
  prepareAbsoluteIosRelease,
4567
7156
  prepareAbsoluteIosDevProject,
4568
7157
  prepareAbsoluteAndroidRelease,
@@ -4575,23 +7164,42 @@ export {
4575
7164
  parseAbsoluteMobileBuildPageMetadata,
4576
7165
  parseAbsoluteIosLogLine,
4577
7166
  parseAbsoluteIosHmrLog,
7167
+ parseAbsoluteAndroidInstalledApp,
7168
+ pairAbsoluteRemoteMac,
4578
7169
  normalizeAbsoluteMobileConfig,
4579
7170
  navigateAbsoluteMobilePage,
7171
+ missingAbsoluteDeviceCapabilityPackages,
7172
+ materializeAbsoluteRemoteMacAgent,
4580
7173
  materializeAbsoluteMobileCompatibilityBundle,
4581
7174
  materializeAbsoluteMobileAssociationFiles,
4582
7175
  materializeAbsoluteCapacitorWebBundle,
4583
7176
  matchesAbsoluteMobileRoutePattern,
4584
7177
  loadAbsoluteNativeReleasePublisher,
4585
7178
  loadAbsoluteMobileMaterializedBundle,
7179
+ loadAbsoluteDeviceCapabilityProviders,
7180
+ listAbsoluteRemoteMacProfiles,
4586
7181
  isAbsoluteIosNativeRootInput,
7182
+ installAbsoluteRemoteMacAgent,
7183
+ installAbsoluteMobileSyncRemediation,
7184
+ installAbsoluteMobileShellHttp,
7185
+ installAbsoluteMobileAuthEnvironment,
7186
+ inspectAbsoluteRemoteMac,
4587
7187
  inspectAbsoluteMobileRouteMetadata,
7188
+ inspectAbsoluteAndroidInstalledApp,
4588
7189
  hashAbsoluteMobilePropsSchema,
4589
7190
  getCurrentAbsoluteMobileProducerContext,
7191
+ getAbsoluteRemoteMacProfile,
7192
+ getAbsoluteMobileSyncRemediation,
4590
7193
  fingerprintAbsoluteIosNativeProject,
4591
7194
  fingerprintAbsoluteIosDevProject,
4592
7195
  finalizeAbsoluteMobilePage,
4593
7196
  finalizeAbsoluteMobileCompatibilityBuild,
4594
7197
  fetchAbsoluteMobilePage,
7198
+ disposeAbsoluteMobilePage,
7199
+ discoverAbsoluteSyncSchema,
7200
+ discoverAbsoluteDeviceCapabilities,
7201
+ directAbsoluteProjectPackages,
7202
+ createAbsoluteRemoteIosDevProject,
4595
7203
  createAbsoluteMobileUpgradeResponse,
4596
7204
  createAbsoluteMobileRouteMetadataPlugin,
4597
7205
  createAbsoluteMobilePageRequest,
@@ -4601,6 +7209,7 @@ export {
4601
7209
  createAbsoluteMobileCompatibilityDispatcher,
4602
7210
  createAbsoluteMobileCompatibilityArtifact,
4603
7211
  createAbsoluteMobileBlobArtifactStore,
7212
+ createAbsoluteMobileAuthManifest,
4604
7213
  createAbsoluteMobileAssociationPlugin,
4605
7214
  createAbsoluteMobileAssociationDocuments,
4606
7215
  createAbsoluteIosNativeWatcher,
@@ -4609,13 +7218,23 @@ export {
4609
7218
  buildAbsoluteMobileCompatibilityRelease,
4610
7219
  buildAbsoluteIosRelease,
4611
7220
  buildAbsoluteAndroidRelease,
7221
+ assertAbsoluteDeviceCapabilityPackages,
7222
+ applyAbsoluteNativeDeviceCapabilities,
4612
7223
  applyAbsoluteNativeDeepLinks,
4613
7224
  activateAbsoluteMobilePage,
4614
7225
  acceptsAbsoluteMobilePage,
7226
+ absoluteRemoteProjectSyncCommands,
7227
+ absoluteRemoteMacSshBase,
7228
+ absoluteDeviceNativeRequirements,
4615
7229
  MOBILE_PAGE_REQUEST_HEADERS,
4616
7230
  AbsoluteMobilePageProtocolError,
4617
7231
  APPLE_ASSOCIATION_PATH,
4618
7232
  ANDROID_ASSOCIATION_PATH,
7233
+ ABSOLUTE_SYNC_PACKAGE,
7234
+ ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
7235
+ ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
7236
+ ABSOLUTE_NATIVE_AUTH_SCOPES,
7237
+ ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
4619
7238
  ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
4620
7239
  ABSOLUTE_MOBILE_ROUTE_DETAIL,
4621
7240
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
@@ -4626,8 +7245,9 @@ export {
4626
7245
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
4627
7246
  ABSOLUTE_IOS_SIMULATOR_NAME,
4628
7247
  ABSOLUTE_IOS_RELEASE_FORMAT,
7248
+ ABSOLUTE_AUTH_PACKAGE,
4629
7249
  ABSOLUTE_ANDROID_RELEASE_FORMAT
4630
7250
  };
4631
7251
 
4632
- //# debugId=048422942BB9C32864756E2164756E21
7252
+ //# debugId=70D407DBFD96952964756E2164756E21
4633
7253
  //# sourceMappingURL=index.js.map