@absolutejs/absolute 0.20.0-beta.2 → 0.20.0-beta.21

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 (75) hide show
  1. package/README.md +135 -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 +1268 -598
  11. package/dist/build.js.map +17 -14
  12. package/dist/cli/index.js +5005 -1614
  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 +9 -3
  16. package/dist/dev/client/hmrTiming.ts +14 -7
  17. package/dist/dev/client/syncDevtools.ts +237 -0
  18. package/dist/index.js +1644 -863
  19. package/dist/index.js.map +26 -23
  20. package/dist/mobile/browser.js +123 -1
  21. package/dist/mobile/browser.js.map +6 -4
  22. package/dist/mobile/index.js +3369 -298
  23. package/dist/mobile/index.js.map +27 -13
  24. package/dist/mobile/remoteMacAgentEntry.js +29 -0
  25. package/dist/mobile/shellAuth.js +35 -0
  26. package/dist/mobile/shellBootstrap.js +585 -0
  27. package/dist/mobile/shellSync.js +123 -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/pageHandlers.d.ts +11 -2
  32. package/dist/src/core/prepare.d.ts +6 -0
  33. package/dist/src/dev/clientManager.d.ts +2 -0
  34. package/dist/src/mobile/androidEmulatorController.d.ts +6 -1
  35. package/dist/src/mobile/browser.d.ts +1 -0
  36. package/dist/src/mobile/buildPipeline.d.ts +1 -0
  37. package/dist/src/mobile/capacitorBundle.d.ts +22 -1
  38. package/dist/src/mobile/client.d.ts +4 -0
  39. package/dist/src/mobile/deviceCapabilities.d.ts +33 -0
  40. package/dist/src/mobile/index.d.ts +9 -0
  41. package/dist/src/mobile/iosConformance.d.ts +15 -0
  42. package/dist/src/mobile/iosNativeWatcher.d.ts +19 -0
  43. package/dist/src/mobile/iosRelease.d.ts +2 -2
  44. package/dist/src/mobile/iosSimulatorController.d.ts +89 -0
  45. package/dist/src/mobile/nativeAuth.d.ts +17 -0
  46. package/dist/src/mobile/nativeBackgroundSync.d.ts +4 -0
  47. package/dist/src/mobile/nativeDeviceCapabilities.d.ts +6 -0
  48. package/dist/src/mobile/releaseArtifact.d.ts +2 -0
  49. package/dist/src/mobile/remoteMacAgent.d.ts +2 -0
  50. package/dist/src/mobile/remoteMacAgentEntry.d.ts +1 -0
  51. package/dist/src/mobile/remoteMacProtocol.d.ts +114 -0
  52. package/dist/src/mobile/remoteMacWire.d.ts +2 -0
  53. package/dist/src/mobile/shellAuth.d.ts +13 -0
  54. package/dist/src/mobile/shellBootstrap.d.ts +18 -1
  55. package/dist/src/mobile/shellSync.d.ts +19 -0
  56. package/dist/src/mobile/staticDocument.d.ts +5 -0
  57. package/dist/src/mobile/syncRemediation.d.ts +10 -0
  58. package/dist/src/mobile/syncSchema.d.ts +9 -0
  59. package/dist/src/mobile/transport.d.ts +16 -1
  60. package/dist/src/plugins/hmr.d.ts +3 -0
  61. package/dist/src/plugins/imageOptimizer.d.ts +1 -1
  62. package/dist/src/svelte/pageHandler.d.ts +3 -0
  63. package/dist/src/utils/imageProcessing.d.ts +3 -0
  64. package/dist/src/utils/loadConfig.d.ts +1 -0
  65. package/dist/src/vue/pageHandler.d.ts +3 -0
  66. package/dist/svelte/index.js +312 -23
  67. package/dist/svelte/index.js.map +7 -4
  68. package/dist/svelte/server.js +307 -18
  69. package/dist/svelte/server.js.map +7 -4
  70. package/dist/types/build.d.ts +14 -0
  71. package/dist/vue/index.js +312 -23
  72. package/dist/vue/index.js.map +7 -4
  73. package/dist/vue/server.js +307 -18
  74. package/dist/vue/server.js.map +7 -4
  75. package/package.json +30 -9
@@ -159,6 +159,436 @@ 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, field) => {
329
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
330
+ throw metadataError(id, `${field} must be a positive safe integer.`);
331
+ return value;
332
+ }, nonEmpty = (value, id, field) => {
333
+ if (typeof value !== "string" || value.trim() !== value || value.length === 0)
334
+ throw metadataError(id, `${field} 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, field) => {
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, field));
347
+ if (object(value))
348
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
349
+ key,
350
+ normalizeJsonValue2(entry, id, field)
351
+ ]));
352
+ throw metadataError(id, `${field} 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 field = nonEmpty(Reflect.get(record, "field"), id, `operation ${index}.field`);
367
+ if (type === "remove-field")
368
+ return { collection, field, type };
369
+ if (type === "set-default")
370
+ return {
371
+ collection,
372
+ field,
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
+
162
592
  // src/mobile/artifactStore.ts
163
593
  import { createHash as createHash2 } from "crypto";
164
594
  import {
@@ -249,13 +679,19 @@ var parseCompatibilityPage = (value) => {
249
679
  if (!isCanonicalRecord(value) || !isPageFramework(value.framework)) {
250
680
  throw new TypeError("Compatibility artifact contains an invalid page.");
251
681
  }
682
+ const styleBundleHash = typeof value.styleBundleHash === "string" ? value.styleBundleHash : undefined;
683
+ const styleBundlePath = typeof value.styleBundlePath === "string" ? value.styleBundlePath : undefined;
684
+ if (Boolean(styleBundleHash) !== Boolean(styleBundlePath)) {
685
+ throw new TypeError("Compatibility page style hash and path must be provided together.");
686
+ }
252
687
  return {
253
688
  bundleHash: readString(value.bundleHash, "page.bundleHash"),
254
689
  bundlePath: readString(value.bundlePath, "page.bundlePath"),
255
690
  contract: readString(value.contract, "page.contract"),
256
691
  framework: value.framework,
257
692
  pageId: readString(value.pageId, "page.pageId"),
258
- propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash")
693
+ propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash"),
694
+ ...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
259
695
  };
260
696
  };
261
697
  var parseCompatibilityRoute = (value) => {
@@ -287,14 +723,23 @@ var validateProducerModule = (module) => {
287
723
  }
288
724
  return module;
289
725
  };
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
- });
726
+ var normalizePage = (page) => {
727
+ if (Boolean(page.styleBundleHash) !== Boolean(page.styleBundlePath)) {
728
+ throw new TypeError("Compatibility page style hash and path must be provided together.");
729
+ }
730
+ return {
731
+ bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
732
+ bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
733
+ contract: requireNonEmpty(page.contract, "page.contract"),
734
+ framework: page.framework,
735
+ pageId: requireNonEmpty(page.pageId, "page.pageId"),
736
+ propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash"),
737
+ ...page.styleBundleHash && page.styleBundlePath ? {
738
+ styleBundleHash: requireNonEmpty(page.styleBundleHash, "page.styleBundleHash"),
739
+ styleBundlePath: requireNonEmpty(page.styleBundlePath, "page.styleBundlePath")
740
+ } : {}
741
+ };
742
+ };
298
743
  var normalizeRoute = (route) => {
299
744
  if (!route.pattern.startsWith("/")) {
300
745
  throw new TypeError("route.pattern must start with /.");
@@ -617,7 +1062,7 @@ var verifyAbsoluteMobileCompatibilityProducer = async (release, maxProducerBytes
617
1062
  // src/mobile/androidRelease.ts
618
1063
  import { createHash as createHash4 } from "crypto";
619
1064
  import {
620
- access as access3,
1065
+ access as access4,
621
1066
  copyFile as copyFile2,
622
1067
  mkdir as mkdir3,
623
1068
  mkdtemp as mkdtemp2,
@@ -630,6 +1075,7 @@ import {
630
1075
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
631
1076
 
632
1077
  // src/mobile/emulatorDoctor.ts
1078
+ import { access } from "fs/promises";
633
1079
  import { homedir } from "os";
634
1080
  import { join as join2 } from "path";
635
1081
 
@@ -647,6 +1093,34 @@ var isWSLEnvironment = () => {
647
1093
  };
648
1094
 
649
1095
  // src/mobile/emulatorDoctor.ts
1096
+ var ABSOLUTE_ANDROID_AVD_NAME = "AbsoluteJS_API_36";
1097
+ var captureCommand = (command) => {
1098
+ try {
1099
+ const result = Bun.spawnSync(command, {
1100
+ stderr: "ignore",
1101
+ stdout: "pipe"
1102
+ });
1103
+ return {
1104
+ exitCode: result.exitCode,
1105
+ stdout: result.stdout.toString()
1106
+ };
1107
+ } catch {
1108
+ return { exitCode: 1, stdout: "" };
1109
+ }
1110
+ };
1111
+ var hasAvailableIosRuntime = (output) => {
1112
+ try {
1113
+ const parsed = JSON.parse(output);
1114
+ if (typeof parsed !== "object" || parsed === null)
1115
+ return false;
1116
+ const runtimes = Reflect.get(parsed, "runtimes");
1117
+ if (!Array.isArray(runtimes))
1118
+ return false;
1119
+ return runtimes.some((runtime) => typeof runtime === "object" && runtime !== null && Reflect.get(runtime, "isAvailable") === true && typeof Reflect.get(runtime, "identifier") === "string" && String(Reflect.get(runtime, "identifier")).includes("iOS"));
1120
+ } catch {
1121
+ return false;
1122
+ }
1123
+ };
650
1124
  var windowsPathToWsl = (path) => {
651
1125
  const match = /^([a-z]):[\\/](.*)$/i.exec(path.trim());
652
1126
  if (!match)
@@ -678,6 +1152,14 @@ var absoluteManagedAndroidSdkRoot = (host, env = process.env) => {
678
1152
  }
679
1153
  return join2(homedir(), ".absolutejs", "android-sdk");
680
1154
  };
1155
+ var pathExists = async (path) => {
1156
+ try {
1157
+ await access(path);
1158
+ return true;
1159
+ } catch {
1160
+ return false;
1161
+ }
1162
+ };
681
1163
  var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironment()) => {
682
1164
  if (platform === "darwin")
683
1165
  return "macos";
@@ -687,10 +1169,151 @@ var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironm
687
1169
  return "wsl";
688
1170
  return "linux";
689
1171
  };
1172
+ var executableNames = (host, name) => {
1173
+ if (host === "wsl")
1174
+ return [`${name}.exe`, `${name}.bat`, name];
1175
+ if (host === "windows")
1176
+ return [name, `${name}.exe`, `${name}.bat`];
1177
+ return [name];
1178
+ };
1179
+ var findExecutable = async (name, paths, options, host) => {
1180
+ const existing = await Promise.all(paths.map(async (path) => await options.exists(path) ? path : undefined));
1181
+ const configured = existing.find((path) => path !== undefined);
1182
+ if (configured)
1183
+ return configured;
1184
+ for (const candidate of executableNames(host, name)) {
1185
+ const path = options.which(candidate);
1186
+ if (path)
1187
+ return path;
1188
+ }
1189
+ return;
1190
+ };
1191
+ var toolCheck = (id, label, platform, path, remediation) => path ? {
1192
+ id,
1193
+ label,
1194
+ path,
1195
+ platform,
1196
+ status: "pass"
1197
+ } : {
1198
+ id,
1199
+ label,
1200
+ platform,
1201
+ remediation,
1202
+ status: "fail"
1203
+ };
1204
+ var inspectAbsoluteMobileToolchain = async (input = {}) => {
1205
+ const env = input.env ?? process.env;
1206
+ const host = input.host ?? detectAbsoluteMobileHost();
1207
+ const exists = input.exists ?? pathExists;
1208
+ const which = input.which ?? ((command) => Bun.which(command));
1209
+ const capture = input.capture ?? captureCommand;
1210
+ const androidRoot = input.androidRoot === null ? undefined : input.androidRoot ?? env.ANDROID_HOME ?? env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host, env);
1211
+ const windowsAndroidTools = host === "windows" || host === "wsl";
1212
+ const android = (segments) => androidRoot ? join2(androidRoot, ...segments) : undefined;
1213
+ const paths = (values) => values.filter((value) => Boolean(value));
1214
+ const adb = await findExecutable("adb", paths([
1215
+ android(["platform-tools", windowsAndroidTools ? "adb.exe" : "adb"])
1216
+ ]), { exists, which }, host);
1217
+ const emulator = await findExecutable("emulator", paths([
1218
+ android([
1219
+ "emulator",
1220
+ windowsAndroidTools ? "emulator.exe" : "emulator"
1221
+ ])
1222
+ ]), { exists, which }, host);
1223
+ const sdkmanager = await findExecutable("sdkmanager", paths([
1224
+ android([
1225
+ "cmdline-tools",
1226
+ "latest",
1227
+ "bin",
1228
+ windowsAndroidTools ? "sdkmanager.bat" : "sdkmanager"
1229
+ ])
1230
+ ]), { exists, which }, host);
1231
+ const avdmanager = await findExecutable("avdmanager", paths([
1232
+ android([
1233
+ "cmdline-tools",
1234
+ "latest",
1235
+ "bin",
1236
+ windowsAndroidTools ? "avdmanager.bat" : "avdmanager"
1237
+ ])
1238
+ ]), { exists, which }, host);
1239
+ const java = await findExecutable("java", [], { exists, which }, host);
1240
+ const checks = [
1241
+ {
1242
+ id: "host",
1243
+ label: `Development host: ${host}`,
1244
+ platform: "host",
1245
+ status: "pass"
1246
+ },
1247
+ toolCheck("android.adb", "Android Debug Bridge", "android", adb, "Install Android SDK Platform Tools or expose adb on PATH."),
1248
+ toolCheck("android.emulator", "Android Emulator", "android", emulator, "Install the Android Emulator from Android Studio SDK Manager."),
1249
+ toolCheck("android.sdkmanager", "Android SDK Manager", "android", sdkmanager, "Install Android SDK Command-line Tools (latest)."),
1250
+ toolCheck("android.avdmanager", "Android Virtual Device Manager", "android", avdmanager, "Install Android SDK Command-line Tools (latest)."),
1251
+ toolCheck("android.java", "Java runtime", "android", java, "Install the JDK required by the configured Android Gradle plugin.")
1252
+ ];
1253
+ if (emulator) {
1254
+ const avds = capture([emulator, "-list-avds"]);
1255
+ const hasManagedAvd = avds.exitCode === 0 && avds.stdout.split(/\r?\n/).includes(ABSOLUTE_ANDROID_AVD_NAME);
1256
+ checks.push({
1257
+ id: "android.avd",
1258
+ label: `AbsoluteJS Android emulator (${ABSOLUTE_ANDROID_AVD_NAME})`,
1259
+ platform: "android",
1260
+ remediation: hasManagedAvd ? undefined : "Run absolute mobile doctor android --fix to provision the managed emulator.",
1261
+ status: hasManagedAvd ? "pass" : "fail"
1262
+ });
1263
+ }
1264
+ if (host === "wsl") {
1265
+ checks.push({
1266
+ id: "android.virtualization",
1267
+ label: adb?.endsWith(".exe") ? "Windows-host Android bridge available to WSL" : "WSL requires a Windows-host emulator bridge or Linux KVM",
1268
+ platform: "android",
1269
+ remediation: adb?.endsWith(".exe") ? undefined : "Expose the Windows Android SDK adb.exe to WSL, or enable /dev/kvm for a Linux SDK.",
1270
+ status: adb?.endsWith(".exe") ? "pass" : "warn"
1271
+ });
1272
+ } else if (host === "linux") {
1273
+ const hasKvm = await exists("/dev/kvm");
1274
+ checks.push({
1275
+ id: "android.virtualization",
1276
+ label: "Linux KVM acceleration",
1277
+ platform: "android",
1278
+ remediation: hasKvm ? undefined : "Enable KVM and grant the current user access to /dev/kvm.",
1279
+ status: hasKvm ? "pass" : "warn"
1280
+ });
1281
+ }
1282
+ if (host !== "macos") {
1283
+ checks.push({
1284
+ id: "ios.simulator",
1285
+ label: "iOS Simulator requires macOS and Xcode",
1286
+ platform: "ios",
1287
+ status: "skip"
1288
+ });
1289
+ return checks;
1290
+ }
1291
+ const xcrun = await findExecutable("xcrun", [], { exists, which }, host);
1292
+ const xcodebuild = await findExecutable("xcodebuild", [], { exists, which }, host);
1293
+ checks.push(toolCheck("ios.xcrun", "Xcode command runner", "ios", xcrun, "Install Xcode and select it with xcode-select."), toolCheck("ios.xcodebuild", "Xcode build system", "ios", xcodebuild, "Install Xcode and select it with xcode-select."));
1294
+ if (xcrun) {
1295
+ const runtimes = capture([
1296
+ xcrun,
1297
+ "simctl",
1298
+ "list",
1299
+ "runtimes",
1300
+ "--json"
1301
+ ]);
1302
+ const hasRuntime = runtimes.exitCode === 0 && hasAvailableIosRuntime(runtimes.stdout);
1303
+ checks.push({
1304
+ id: "ios.runtime",
1305
+ label: "iOS Simulator runtime",
1306
+ platform: "ios",
1307
+ remediation: hasRuntime ? undefined : "Run absolute mobile doctor ios --fix to download an iOS Simulator runtime.",
1308
+ status: hasRuntime ? "pass" : "fail"
1309
+ });
1310
+ }
1311
+ return checks;
1312
+ };
690
1313
 
691
1314
  // src/mobile/androidEmulatorController.ts
692
1315
  import {
693
- access as access2,
1316
+ access as access3,
694
1317
  copyFile,
695
1318
  lstat,
696
1319
  mkdir as mkdir2,
@@ -714,7 +1337,7 @@ import {
714
1337
  } from "path";
715
1338
 
716
1339
  // src/mobile/capacitorProject.ts
717
- import { access, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
1340
+ import { access as access2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
718
1341
  import { relative, resolve } from "path";
719
1342
  var CONFIG_FILE = "capacitor.config.ts";
720
1343
  var portableRelative = (root, path) => relative(root, path).replaceAll("\\", "/");
@@ -736,7 +1359,7 @@ export default config;
736
1359
  `;
737
1360
  var exists = async (path) => {
738
1361
  try {
739
- await access(path);
1362
+ await access2(path);
740
1363
  return true;
741
1364
  } catch {
742
1365
  return false;
@@ -766,9 +1389,9 @@ var HASH_RADIX = 16;
766
1389
  var EXECUTABLE_MODE_MASK = 73;
767
1390
  var NATIVE_PUBLIC_PATH_SEGMENTS = 5;
768
1391
  var CAPACITOR_PROJECT_DIRECTORY_PATTERN = /project\(['"](:[^'"]+)['"]\)\.projectDir\s*=\s*new File\(['"]([^'"]+)['"]\)/gu;
769
- var pathExists = async (path) => {
1392
+ var pathExists2 = async (path) => {
770
1393
  try {
771
- await access2(path);
1394
+ await access3(path);
772
1395
  return true;
773
1396
  } catch {
774
1397
  return false;
@@ -795,7 +1418,7 @@ var runCommand = async (command, options = {}) => {
795
1418
  ]);
796
1419
  return exitCode;
797
1420
  };
798
- var captureCommand = (command, options = {}) => {
1421
+ var captureCommand2 = (command, options = {}) => {
799
1422
  try {
800
1423
  const result = Bun.spawnSync(command, {
801
1424
  cwd: options.cwd,
@@ -881,11 +1504,11 @@ var hashNativeTree = async (root, label, ignorePublicBundle) => {
881
1504
  const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
882
1505
  return createHash3("sha256").update(records.join("")).digest("hex");
883
1506
  };
884
- var fingerprintAbsoluteAndroidNativeProject = async (project) => {
1507
+ var fingerprintAbsoluteAndroidNativeProject = async (project, options = {}) => {
885
1508
  const { dependencies } = await nativeDependencySources(project.nativeDirectory);
886
1509
  const roots = [
887
1510
  {
888
- ignorePublicBundle: true,
1511
+ ignorePublicBundle: options.includePublicBundle !== true,
889
1512
  label: "android",
890
1513
  source: project.nativeDirectory
891
1514
  },
@@ -965,14 +1588,14 @@ var gradleArtifactPath = (nativeDirectory, task, windows = false) => {
965
1588
  };
966
1589
  var resolveGradleArtifactPath = async (nativeDirectory, task) => {
967
1590
  const primary = gradleArtifactPath(nativeDirectory, task);
968
- if (task !== "assembleRelease" || await pathExists(primary)) {
1591
+ if (task !== "assembleRelease" || await pathExists2(primary)) {
969
1592
  return primary;
970
1593
  }
971
1594
  return join3(nativeDirectory, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk");
972
1595
  };
973
1596
  var buildAbsoluteAndroidGradleArtifact = async (options) => {
974
1597
  const { project, task } = options;
975
- const capture = options.capture ?? captureCommand;
1598
+ const capture = options.capture ?? captureCommand2;
976
1599
  const run = options.run ?? runCommand;
977
1600
  const env = options.env ?? process.env;
978
1601
  const gradleArguments = options.gradleArguments ?? [];
@@ -1015,9 +1638,9 @@ var requireManifest = (value) => {
1015
1638
  runtime: value.runtime
1016
1639
  };
1017
1640
  };
1018
- var pathExists2 = async (path) => {
1641
+ var pathExists3 = async (path) => {
1019
1642
  try {
1020
- await access3(path);
1643
+ await access4(path);
1021
1644
  return true;
1022
1645
  } catch {
1023
1646
  return false;
@@ -1072,7 +1695,7 @@ var installRelease = async (artifactPath, metadata, outputRoot) => {
1072
1695
  const releaseRoot = join4(outputRoot, metadata.releaseId);
1073
1696
  const artifactName = "app-release.aab";
1074
1697
  const destination = join4(releaseRoot, artifactName);
1075
- if (await pathExists2(releaseRoot)) {
1698
+ if (await pathExists3(releaseRoot)) {
1076
1699
  const existing = requireManifestIdentity(JSON.parse(await readFile4(join4(releaseRoot, "release.json"), "utf8")), metadata);
1077
1700
  const [installedBytes, installedSha256] = await Promise.all([
1078
1701
  stat(destination).then(({ size }) => size),
@@ -1148,7 +1771,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
1148
1771
  run: options.run,
1149
1772
  task: "bundleRelease"
1150
1773
  });
1151
- if (!await pathExists2(artifactPath)) {
1774
+ if (!await pathExists3(artifactPath)) {
1152
1775
  throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
1153
1776
  }
1154
1777
  const capture = options.capture ?? defaultCapture;
@@ -1183,7 +1806,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
1183
1806
  // src/mobile/iosRelease.ts
1184
1807
  import { createHash as createHash5 } from "crypto";
1185
1808
  import {
1186
- access as access4,
1809
+ access as access5,
1187
1810
  copyFile as copyFile3,
1188
1811
  mkdir as mkdir4,
1189
1812
  mkdtemp as mkdtemp3,
@@ -1207,9 +1830,9 @@ var requireManifest2 = (value) => {
1207
1830
  runtime: value.runtime
1208
1831
  };
1209
1832
  };
1210
- var pathExists3 = async (path) => {
1833
+ var pathExists4 = async (path) => {
1211
1834
  try {
1212
- await access4(path);
1835
+ await access5(path);
1213
1836
  return true;
1214
1837
  } catch {
1215
1838
  return false;
@@ -1290,7 +1913,7 @@ var safeOutputDirectory2 = (projectRoot, requested) => {
1290
1913
  };
1291
1914
  var sha256File2 = async (path) => createHash5("sha256").update(await readFile5(path)).digest("hex");
1292
1915
  var findByExtension = async (root, extension) => {
1293
- if (!await pathExists3(root))
1916
+ if (!await pathExists4(root))
1294
1917
  return;
1295
1918
  const entries = await readdir3(root, { withFileTypes: true });
1296
1919
  const matches = await Promise.all(entries.map(async (entry) => {
@@ -1322,7 +1945,7 @@ var requireBuildNumber = (value) => {
1322
1945
  var installRelease2 = async (artifactPath, metadata, outputRoot) => {
1323
1946
  const releaseRoot = join5(outputRoot, metadata.releaseId);
1324
1947
  const destination = join5(releaseRoot, "App.ipa");
1325
- if (await pathExists3(releaseRoot)) {
1948
+ if (await pathExists4(releaseRoot)) {
1326
1949
  const value = JSON.parse(await readFile5(join5(releaseRoot, "release.json"), "utf8"));
1327
1950
  if (!isRecord2(value) || value.artifact !== "App.ipa" || Object.entries(metadata).some(([key, expected]) => Reflect.get(value, key) !== expected)) {
1328
1951
  throw new TypeError(`Immutable iOS release ${metadata.releaseId} does not match its content.`);
@@ -1459,30 +2082,1502 @@ var buildAbsoluteIosRelease = async (options) => {
1459
2082
  await rm4(staging, { force: true, recursive: true }).catch(() => {
1460
2083
  return;
1461
2084
  });
1462
- }
2085
+ }
2086
+ };
2087
+ // src/mobile/iosConformance.ts
2088
+ import { readFile as readFile6, stat as stat3 } from "fs/promises";
2089
+ var HMR_LINE = new RegExp(String.raw`\[hmr:ios\]\s+([^\n]*?)\s+(applied in|falling back to reload after|failed after)\s+(\d+)ms(?:; server\s+(\d+)ms, client\s+(\d+)ms)?`, "u");
2090
+ var parseAbsoluteIosHmrLog = (line) => {
2091
+ const match = HMR_LINE.exec(line);
2092
+ if (!match)
2093
+ return null;
2094
+ const [, , action, durationValue, serverValue, clientValue] = match;
2095
+ let outcome = "reloaded";
2096
+ if (action === "applied in")
2097
+ outcome = "applied";
2098
+ if (action === "failed after")
2099
+ outcome = "failed";
2100
+ const serverMs = serverValue === undefined ? undefined : Number(serverValue);
2101
+ const clientMs = clientValue === undefined ? undefined : Number(clientValue);
2102
+ return {
2103
+ ...clientMs === undefined ? {} : { clientMs },
2104
+ duration: Number(durationValue),
2105
+ line: match[0],
2106
+ outcome,
2107
+ ...serverMs === undefined ? {} : { serverMs }
2108
+ };
2109
+ };
2110
+ var findHmrApply = (lines) => {
2111
+ const apply = lines.map((line) => parseAbsoluteIosHmrLog(line)).find((candidate) => candidate !== null);
2112
+ if (apply?.outcome === "failed")
2113
+ throw new Error(`iOS HMR client reported a failed apply: ${apply.line}`);
2114
+ return apply;
2115
+ };
2116
+ var waitForAbsoluteIosHmrLog = async (options) => {
2117
+ const sleep = options.sleep ?? Bun.sleep;
2118
+ const timeoutMs = options.timeoutMs ?? 30000;
2119
+ const deadline = Date.now() + timeoutMs;
2120
+ let offset = options.startOffset ?? await stat3(options.logPath).then(({ size }) => size).catch(() => 0);
2121
+ let buffered = "";
2122
+ const poll = async () => {
2123
+ if (Date.now() > deadline)
2124
+ throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
2125
+ options.signal?.throwIfAborted();
2126
+ const contents = await readFile6(options.logPath).catch(() => Buffer.alloc(0));
2127
+ if (contents.byteLength < offset) {
2128
+ offset = 0;
2129
+ buffered = "";
2130
+ }
2131
+ if (contents.byteLength > offset) {
2132
+ buffered += contents.subarray(offset).toString("utf8");
2133
+ offset = contents.byteLength;
2134
+ const lines = buffered.split(/\r?\n/u);
2135
+ buffered = lines.pop() ?? "";
2136
+ const apply = findHmrApply(lines);
2137
+ if (apply)
2138
+ return apply;
2139
+ }
2140
+ await sleep(100);
2141
+ return poll();
2142
+ };
2143
+ return poll();
2144
+ };
2145
+ // src/mobile/iosSimulatorController.ts
2146
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "crypto";
2147
+ import {
2148
+ access as access6,
2149
+ copyFile as copyFile4,
2150
+ mkdir as mkdir5,
2151
+ readFile as readFile7,
2152
+ rename as rename6,
2153
+ rm as rm5,
2154
+ writeFile as writeFile6
2155
+ } from "fs/promises";
2156
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join6, relative as relative5, resolve as resolve5, sep as sep4 } from "path";
2157
+ init_getDurationString();
2158
+ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone";
2159
+ var BOOT_TIMEOUT_MS = 180000;
2160
+ var BOOT_POLL_MS = 1000;
2161
+ var DEV_JOURNAL_FORMAT = 1;
2162
+ var NATIVE_CACHE_FORMAT = 1;
2163
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2164
+ var pathExists5 = async (path) => {
2165
+ try {
2166
+ await access6(path);
2167
+ return true;
2168
+ } catch {
2169
+ return false;
2170
+ }
2171
+ };
2172
+ var throwIfAborted2 = (signal) => signal?.throwIfAborted();
2173
+ var defaultCapture3 = (command, options = {}) => {
2174
+ try {
2175
+ const result = Bun.spawnSync(command, {
2176
+ cwd: options.cwd,
2177
+ env: options.env,
2178
+ stderr: "pipe",
2179
+ stdin: "ignore",
2180
+ stdout: "pipe"
2181
+ });
2182
+ return {
2183
+ exitCode: result.exitCode,
2184
+ stderr: result.stderr.toString(),
2185
+ stdout: result.stdout.toString()
2186
+ };
2187
+ } catch (error) {
2188
+ return {
2189
+ exitCode: 1,
2190
+ stderr: error instanceof Error ? error.message : String(error),
2191
+ stdout: ""
2192
+ };
2193
+ }
2194
+ };
2195
+ var defaultRun2 = async (command, options = {}) => {
2196
+ const process2 = Bun.spawn(command, {
2197
+ cwd: options.cwd,
2198
+ env: options.env,
2199
+ signal: options.signal,
2200
+ stderr: "inherit",
2201
+ stdin: "inherit",
2202
+ stdout: "inherit"
2203
+ });
2204
+ return process2.exited;
2205
+ };
2206
+ var defaultSpawn = (command, options = {}) => {
2207
+ Bun.spawn(command, {
2208
+ cwd: options.cwd,
2209
+ env: options.env,
2210
+ signal: options.signal,
2211
+ stderr: "ignore",
2212
+ stdin: "ignore",
2213
+ stdout: "ignore"
2214
+ });
2215
+ };
2216
+ var consumeLines = async (stream, onLine) => {
2217
+ const reader = stream.getReader();
2218
+ const decoder = new TextDecoder;
2219
+ let buffered = "";
2220
+ const pump = async () => {
2221
+ const { done, value } = await reader.read();
2222
+ if (done)
2223
+ return;
2224
+ buffered += decoder.decode(value, { stream: true });
2225
+ const lines = buffered.split(/\r?\n/u);
2226
+ buffered = lines.pop() ?? "";
2227
+ lines.forEach(onLine);
2228
+ await pump();
2229
+ };
2230
+ try {
2231
+ await pump();
2232
+ buffered += decoder.decode();
2233
+ if (buffered)
2234
+ onLine(buffered);
2235
+ } finally {
2236
+ reader.releaseLock();
2237
+ }
2238
+ };
2239
+ var defaultStartNativeLogs = (command, options, onLine) => {
2240
+ const process2 = Bun.spawn(command, {
2241
+ cwd: options.cwd,
2242
+ env: options.env,
2243
+ signal: options.signal,
2244
+ stderr: "pipe",
2245
+ stdin: "ignore",
2246
+ stdout: "pipe"
2247
+ });
2248
+ consumeLines(process2.stdout, onLine);
2249
+ consumeLines(process2.stderr, onLine);
2250
+ return {
2251
+ close: async () => {
2252
+ try {
2253
+ process2.kill();
2254
+ } catch {}
2255
+ await process2.exited.catch(() => {
2256
+ return;
2257
+ });
2258
+ }
2259
+ };
2260
+ };
2261
+ var requireSuccess2 = async (command, label, run, options) => {
2262
+ const exitCode = await run(command, options);
2263
+ if (exitCode !== 0)
2264
+ throw new Error(`${label} failed with status ${exitCode}.`);
2265
+ };
2266
+ var requireCapturedSuccess = (result, label) => {
2267
+ if (result.exitCode !== 0) {
2268
+ throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
2269
+ }
2270
+ return result.stdout.trim();
2271
+ };
2272
+ var parseJson = (source, label) => {
2273
+ try {
2274
+ const parsed = JSON.parse(source);
2275
+ if (isRecord3(parsed))
2276
+ return parsed;
2277
+ } catch {}
2278
+ throw new Error(`Invalid ${label} JSON from simctl.`);
2279
+ };
2280
+ var parseIosDeviceTypes = (source) => {
2281
+ const parsed = parseJson(source, "device type");
2282
+ const types = parsed.devicetypes;
2283
+ if (!Array.isArray(types))
2284
+ return [];
2285
+ return types.flatMap((type) => {
2286
+ if (!isRecord3(type))
2287
+ return [];
2288
+ const { identifier } = type;
2289
+ const { name } = type;
2290
+ return typeof identifier === "string" && typeof name === "string" ? [{ identifier, name }] : [];
2291
+ });
2292
+ };
2293
+ var parseIosRuntimes = (source) => {
2294
+ const parsed = parseJson(source, "runtime");
2295
+ const { runtimes } = parsed;
2296
+ if (!Array.isArray(runtimes))
2297
+ return [];
2298
+ return runtimes.flatMap((runtime) => {
2299
+ if (!isRecord3(runtime))
2300
+ return [];
2301
+ const { identifier } = runtime;
2302
+ const { name } = runtime;
2303
+ const { version } = runtime;
2304
+ if (typeof identifier !== "string" || typeof name !== "string" || typeof version !== "string")
2305
+ return [];
2306
+ return [
2307
+ {
2308
+ identifier,
2309
+ isAvailable: runtime.isAvailable !== false,
2310
+ name,
2311
+ version
2312
+ }
2313
+ ];
2314
+ });
2315
+ };
2316
+ var parseIosSimulators = (source) => {
2317
+ const parsed = parseJson(source, "device");
2318
+ const { devices } = parsed;
2319
+ if (!isRecord3(devices))
2320
+ return [];
2321
+ return Object.entries(devices).flatMap(([runtime, values]) => {
2322
+ if (!Array.isArray(values))
2323
+ return [];
2324
+ return values.flatMap((device) => {
2325
+ if (!isRecord3(device))
2326
+ return [];
2327
+ const { name } = device;
2328
+ const { state } = device;
2329
+ const { udid } = device;
2330
+ if (typeof name !== "string" || typeof state !== "string" || typeof udid !== "string")
2331
+ return [];
2332
+ return [
2333
+ {
2334
+ isAvailable: device.isAvailable !== false,
2335
+ name,
2336
+ runtime,
2337
+ state,
2338
+ udid
2339
+ }
2340
+ ];
2341
+ });
2342
+ });
2343
+ };
2344
+ var versionParts = (version) => version.split(".").map((part) => Number(part));
2345
+ var compareVersions = (left, right) => {
2346
+ const leftParts = versionParts(left);
2347
+ const rightParts = versionParts(right);
2348
+ const length = Math.max(leftParts.length, rightParts.length);
2349
+ for (let index = 0;index < length; index++) {
2350
+ const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
2351
+ if (difference !== 0)
2352
+ return difference;
2353
+ }
2354
+ return 0;
2355
+ };
2356
+ var latestIosRuntime = (runtimes) => runtimes.filter((runtime) => runtime.isAvailable && runtime.identifier.includes("SimRuntime.iOS-")).sort((left, right) => compareVersions(right.version, left.version))[0];
2357
+ var iphoneGeneration = (name) => Number(/iPhone\s+(\d+)/u.exec(name)?.[1] ?? 0);
2358
+ var preferredIphoneType = (types) => types.filter((type) => type.name.startsWith("iPhone")).sort((left, right) => {
2359
+ const generation = iphoneGeneration(right.name) - iphoneGeneration(left.name);
2360
+ if (generation !== 0)
2361
+ return generation;
2362
+ const rightPro = right.name.includes("Pro") ? 1 : 0;
2363
+ const leftPro = left.name.includes("Pro") ? 1 : 0;
2364
+ return rightPro - leftPro;
2365
+ })[0];
2366
+ var journalPaths = (projectRoot) => {
2367
+ const root = join6(projectRoot, ".absolutejs", "mobile", "ios-dev-session");
2368
+ return {
2369
+ configBackup: join6(root, "capacitor-config.backup"),
2370
+ infoBackup: join6(root, "Info.plist.backup"),
2371
+ journal: join6(root, "journal.json"),
2372
+ root
2373
+ };
2374
+ };
2375
+ var nativeCachePath = (projectRoot) => join6(projectRoot, ".absolutejs", "mobile", "ios-native-cache.json");
2376
+ var isInside = (root, path) => {
2377
+ const value = relative5(resolve5(root), resolve5(path));
2378
+ return value === "" || !value.startsWith(`..${sep4}`) && value !== ".." && !isAbsolute4(value);
2379
+ };
2380
+ var parseJournal = (value) => {
2381
+ if (!isRecord3(value) || value.format !== DEV_JOURNAL_FORMAT)
2382
+ return null;
2383
+ const { configBackupPath } = value;
2384
+ const { infoBackupPath } = value;
2385
+ const { infoPath } = value;
2386
+ const { nativeConfigPath } = value;
2387
+ if (typeof configBackupPath !== "string" || typeof infoBackupPath !== "string" || typeof infoPath !== "string" || typeof nativeConfigPath !== "string")
2388
+ return null;
2389
+ return {
2390
+ configBackupPath,
2391
+ format: DEV_JOURNAL_FORMAT,
2392
+ infoBackupPath,
2393
+ infoPath,
2394
+ nativeConfigPath
2395
+ };
2396
+ };
2397
+ var repairAbsoluteIosDevSession = async (projectRoot) => {
2398
+ const paths = journalPaths(projectRoot);
2399
+ if (!await pathExists5(paths.journal)) {
2400
+ await rm5(paths.root, { force: true, recursive: true });
2401
+ return false;
2402
+ }
2403
+ const journal = await readFile7(paths.journal, "utf8").then((source) => parseJournal(JSON.parse(source))).catch(() => null);
2404
+ if (!journal || !isInside(projectRoot, journal.nativeConfigPath) || !isInside(projectRoot, journal.infoPath) || !isInside(paths.root, journal.configBackupPath) || !isInside(paths.root, journal.infoBackupPath)) {
2405
+ throw new Error(`Refusing unsafe or invalid iOS dev journal at ${paths.journal}.`);
2406
+ }
2407
+ if (await pathExists5(journal.configBackupPath))
2408
+ await copyFile4(journal.configBackupPath, journal.nativeConfigPath);
2409
+ if (await pathExists5(journal.infoBackupPath))
2410
+ await copyFile4(journal.infoBackupPath, journal.infoPath);
2411
+ await rm5(paths.root, { force: true, recursive: true });
2412
+ return true;
2413
+ };
2414
+ var iosDevelopmentInfoPlist = (source, cleartext) => {
2415
+ if (!cleartext)
2416
+ return source;
2417
+ const arbitraryLoads = /(<key>NSAllowsArbitraryLoads<\/key>\s*)<false\s*\/>/u;
2418
+ if (arbitraryLoads.test(source))
2419
+ return source.replace(arbitraryLoads, "$1<true/>");
2420
+ if (/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(source))
2421
+ return source;
2422
+ const transport = /(<key>NSAppTransportSecurity<\/key>\s*<dict>)/u;
2423
+ if (transport.test(source))
2424
+ return source.replace(transport, `$1
2425
+ <key>NSAllowsArbitraryLoads</key>
2426
+ <true/>`);
2427
+ return source.replace(/<dict>/u, `<dict>
2428
+ <key>NSAppTransportSecurity</key>
2429
+ <dict>
2430
+ <key>NSAllowsArbitraryLoads</key>
2431
+ <true/>
2432
+ </dict>`);
2433
+ };
2434
+ var writeDevProjection = async (project, port, https) => {
2435
+ const paths = journalPaths(project.projectRoot);
2436
+ await repairAbsoluteIosDevSession(project.projectRoot);
2437
+ const nativeConfigPath = join6(project.nativeDirectory, "App", "App", "capacitor.config.json");
2438
+ const infoPath = join6(project.nativeDirectory, "App", "App", "Info.plist");
2439
+ const [configSource, infoSource] = await Promise.all([
2440
+ readFile7(nativeConfigPath, "utf8"),
2441
+ readFile7(infoPath, "utf8")
2442
+ ]);
2443
+ const parsed = JSON.parse(configSource);
2444
+ if (!isRecord3(parsed))
2445
+ throw new Error(`Invalid Capacitor native config at ${nativeConfigPath}.`);
2446
+ await mkdir5(paths.root, { recursive: true });
2447
+ await Promise.all([
2448
+ writeFile6(paths.configBackup, configSource, { flag: "wx" }),
2449
+ writeFile6(paths.infoBackup, infoSource, { flag: "wx" })
2450
+ ]);
2451
+ const journal = {
2452
+ configBackupPath: paths.configBackup,
2453
+ format: DEV_JOURNAL_FORMAT,
2454
+ infoBackupPath: paths.infoBackup,
2455
+ infoPath,
2456
+ nativeConfigPath
2457
+ };
2458
+ await writeFile6(paths.journal, `${JSON.stringify(journal, null, "\t")}
2459
+ `, {
2460
+ flag: "wx"
2461
+ });
2462
+ const developmentUrl = new URL(`${https ? "https" : "http"}://localhost:${port}${project.config.entry}`);
2463
+ developmentUrl.searchParams.set("__absolute_target", "capacitor-ios");
2464
+ const existingServer = parsed.server;
2465
+ parsed.server = {
2466
+ ...isRecord3(existingServer) ? existingServer : {},
2467
+ cleartext: !https,
2468
+ url: developmentUrl.href
2469
+ };
2470
+ await Promise.all([
2471
+ writeFile6(nativeConfigPath, `${JSON.stringify(parsed, null, "\t")}
2472
+ `),
2473
+ writeFile6(infoPath, iosDevelopmentInfoPlist(infoSource, !https))
2474
+ ]);
2475
+ };
2476
+ var parseNativeCache = (value) => {
2477
+ if (!isRecord3(value))
2478
+ return null;
2479
+ const { appId, fingerprint, format, installations } = value;
2480
+ if (format !== NATIVE_CACHE_FORMAT || typeof appId !== "string" || typeof fingerprint !== "string" || !isRecord3(installations) || !Object.values(installations).every((identity) => typeof identity === "string"))
2481
+ return null;
2482
+ return {
2483
+ appId,
2484
+ fingerprint,
2485
+ format,
2486
+ installations: Object.fromEntries(Object.entries(installations).map(([udid, identity]) => [
2487
+ udid,
2488
+ String(identity)
2489
+ ]))
2490
+ };
2491
+ };
2492
+ var readNativeCache = (projectRoot) => readFile7(nativeCachePath(projectRoot), "utf8").then((source) => parseNativeCache(JSON.parse(source))).catch(() => null);
2493
+ var writeNativeCache = async (projectRoot, cache) => {
2494
+ const destination = nativeCachePath(projectRoot);
2495
+ const temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
2496
+ await mkdir5(dirname4(destination), { recursive: true });
2497
+ try {
2498
+ await writeFile6(temporary, `${JSON.stringify(cache, null, "\t")}
2499
+ `, {
2500
+ flag: "wx"
2501
+ });
2502
+ await rename6(temporary, destination);
2503
+ } finally {
2504
+ await rm5(temporary, { force: true }).catch(() => {
2505
+ return;
2506
+ });
2507
+ }
2508
+ };
2509
+ var fingerprintAbsoluteIosDevProject = async (project) => fingerprintAbsoluteIosNativeProject(project.nativeDirectory);
2510
+ var simulatorInventory = (xcrun, capture) => {
2511
+ const result = capture([
2512
+ xcrun,
2513
+ "simctl",
2514
+ "list",
2515
+ "devices",
2516
+ "available",
2517
+ "-j"
2518
+ ]);
2519
+ return parseIosSimulators(requireCapturedSuccess(result, "iOS simulator discovery"));
2520
+ };
2521
+ var ensureManagedSimulator = async (project, capture) => {
2522
+ const runtimes = parseIosRuntimes(requireCapturedSuccess(capture([project.xcrun, "simctl", "list", "runtimes", "-j"]), "iOS runtime discovery"));
2523
+ const runtime = latestIosRuntime(runtimes);
2524
+ if (!runtime)
2525
+ throw new Error("No available iOS Simulator runtime. Run absolute mobile doctor ios --fix.");
2526
+ const [existing] = simulatorInventory(project.xcrun, capture).filter((device) => device.isAvailable && device.name === ABSOLUTE_IOS_SIMULATOR_NAME && device.runtime === runtime.identifier).sort((left, right) => Number(right.state === "Booted") - Number(left.state === "Booted"));
2527
+ if (existing)
2528
+ return { created: false, device: existing };
2529
+ const types = parseIosDeviceTypes(requireCapturedSuccess(capture([project.xcrun, "simctl", "list", "devicetypes", "-j"]), "iOS device-type discovery"));
2530
+ const type = preferredIphoneType(types);
2531
+ if (!type)
2532
+ throw new Error("Xcode did not report an iPhone simulator type.");
2533
+ const [udid] = requireCapturedSuccess(capture([
2534
+ project.xcrun,
2535
+ "simctl",
2536
+ "create",
2537
+ ABSOLUTE_IOS_SIMULATOR_NAME,
2538
+ type.identifier,
2539
+ runtime.identifier
2540
+ ]), "iOS simulator creation").split(/\s/u);
2541
+ if (!udid)
2542
+ throw new Error("simctl did not return the created simulator UDID.");
2543
+ return {
2544
+ created: true,
2545
+ device: {
2546
+ isAvailable: true,
2547
+ name: ABSOLUTE_IOS_SIMULATOR_NAME,
2548
+ runtime: runtime.identifier,
2549
+ state: "Shutdown",
2550
+ udid
2551
+ }
2552
+ };
2553
+ };
2554
+ var waitForBootedSimulator = async (project, udid, capture, sleep, signal) => {
2555
+ const deadline = Date.now() + BOOT_TIMEOUT_MS;
2556
+ const poll = async () => {
2557
+ throwIfAborted2(signal);
2558
+ const device = simulatorInventory(project.xcrun, capture).find((candidate) => candidate.udid === udid);
2559
+ if (device?.state === "Booted")
2560
+ return;
2561
+ if (Date.now() > deadline)
2562
+ throw new Error(`iOS simulator ${udid} did not finish booting within ${BOOT_TIMEOUT_MS / 1000}s.`);
2563
+ await sleep(BOOT_POLL_MS);
2564
+ await poll();
2565
+ };
2566
+ return poll();
2567
+ };
2568
+ var bootSimulator = (project, device, capture) => {
2569
+ if (device.state === "Booted")
2570
+ return;
2571
+ requireCapturedSuccess(capture([project.xcrun, "simctl", "boot", device.udid]), "iOS simulator boot");
2572
+ };
2573
+ var installedAppIdentity = (project, udid, capture) => {
2574
+ const result = capture([
2575
+ project.xcrun,
2576
+ "simctl",
2577
+ "get_app_container",
2578
+ udid,
2579
+ project.config.appId,
2580
+ "app"
2581
+ ]);
2582
+ return result.exitCode === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
2583
+ };
2584
+ var buildIosDebugApp = async (project, udid, fingerprint, run, signal) => {
2585
+ const derivedDataPath = join6(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash6("sha256").update(project.config.appId).digest("hex").slice(0, 16));
2586
+ await mkdir5(derivedDataPath, { recursive: true });
2587
+ await requireSuccess2([
2588
+ project.xcodebuild,
2589
+ "-workspace",
2590
+ join6(project.nativeDirectory, "App", "App.xcworkspace"),
2591
+ "-scheme",
2592
+ "App",
2593
+ "-configuration",
2594
+ "Debug",
2595
+ "-destination",
2596
+ `platform=iOS Simulator,id=${udid}`,
2597
+ "-derivedDataPath",
2598
+ derivedDataPath,
2599
+ "build"
2600
+ ], "iOS simulator build", run, { cwd: project.nativeDirectory, signal });
2601
+ const appPath = join6(derivedDataPath, "Build", "Products", "Debug-iphonesimulator", "App.app");
2602
+ if (!await pathExists5(appPath))
2603
+ throw new Error(`Xcode did not produce the simulator app at ${appPath}.`);
2604
+ return appPath;
2605
+ };
2606
+ var ensureIosDebugApp = async (options) => {
2607
+ const installed = installedAppIdentity(options.project, options.udid, options.capture);
2608
+ const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && installed !== undefined && options.cache.installations[options.udid] === installed;
2609
+ if (cacheHit) {
2610
+ options.log(`iOS native app is unchanged on ${options.udid}; skipped Xcode build and install.`);
2611
+ return true;
2612
+ }
2613
+ options.log("iOS native inputs changed or the installed app is stale; rebuilding.");
2614
+ options.transition("building");
2615
+ const appPath = await buildIosDebugApp(options.project, options.udid, options.fingerprint, options.run, options.signal);
2616
+ throwIfAborted2(options.signal);
2617
+ options.transition("installing");
2618
+ await requireSuccess2([options.project.xcrun, "simctl", "install", options.udid, appPath], "iOS simulator app installation", options.run, { signal: options.signal });
2619
+ const updated = installedAppIdentity(options.project, options.udid, options.capture);
2620
+ if (updated) {
2621
+ await writeNativeCache(options.project.projectRoot, {
2622
+ appId: options.project.config.appId,
2623
+ fingerprint: options.fingerprint,
2624
+ format: NATIVE_CACHE_FORMAT,
2625
+ installations: { [options.udid]: updated }
2626
+ }).catch((error) => options.log(`iOS native cache could not be saved: ${error instanceof Error ? error.message : String(error)}`));
2627
+ }
2628
+ return false;
2629
+ };
2630
+ var SECRET_VALUE = /((?:authorization|cookie|password|secret|token|oauth[_-]?code)\s*[:=]\s*)([^\s,;]+)/giu;
2631
+ var BEARER_VALUE = new RegExp(String.raw`\bBearer\s+[A-Za-z0-9._~+/-]+=*`, "giu");
2632
+ var JWT_VALUE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu;
2633
+ var IOS_LOG_PATTERN = new RegExp(String.raw`\s(Debug|Info|Notice|Error|Fault)\s+.*?\[[^\]]+\]\s+\[([^\]]+)\]\s*(.*)$`, "iu");
2634
+ var parseAbsoluteIosLogLine = (line) => {
2635
+ const sanitized = redactAbsoluteIosLog(line).trim();
2636
+ if (!sanitized)
2637
+ return null;
2638
+ const match = IOS_LOG_PATTERN.exec(sanitized);
2639
+ const candidate = match?.[1]?.toLowerCase();
2640
+ const level = candidate === "debug" || candidate === "error" || candidate === "fault" || candidate === "notice" ? candidate : "info";
2641
+ return {
2642
+ level,
2643
+ message: match?.[3]?.trim() || sanitized,
2644
+ tag: match?.[2]?.trim() || "App"
2645
+ };
2646
+ };
2647
+ var redactAbsoluteIosLog = (value) => value.replaceAll(BEARER_VALUE, "Bearer [REDACTED]").replaceAll(JWT_VALUE, "[REDACTED_JWT]").replaceAll(SECRET_VALUE, "$1[REDACTED]").replaceAll(/\p{C}/gu, "");
2648
+ var attachNativeLogs = (project, udid, options) => {
2649
+ if (!options.nativeLog)
2650
+ return null;
2651
+ const start = options.startNativeLogs ?? defaultStartNativeLogs;
2652
+ return start([
2653
+ project.xcrun,
2654
+ "simctl",
2655
+ "spawn",
2656
+ udid,
2657
+ "log",
2658
+ "stream",
2659
+ "--style",
2660
+ "compact",
2661
+ "--level",
2662
+ "debug",
2663
+ "--predicate",
2664
+ 'process == "App"'
2665
+ ], { signal: options.signal }, (line) => {
2666
+ const entry = parseAbsoluteIosLogLine(line);
2667
+ if (entry)
2668
+ options.nativeLog?.(entry);
2669
+ });
2670
+ };
2671
+ var IOS_TIMING_PHASES = [
2672
+ ["syncing", "Capacitor sync"],
2673
+ ["configuring", "dev config"],
2674
+ ["fingerprinting", "fingerprint"],
2675
+ ["booting", "simulator"],
2676
+ ["connecting", "device ready"],
2677
+ ["checking-native", "app check"],
2678
+ ["building", "Xcode"],
2679
+ ["installing", "install"],
2680
+ ["launching", "launch"],
2681
+ ["streaming-logs", "logs"]
2682
+ ];
2683
+ var timingSummary = (timings) => IOS_TIMING_PHASES.map(([phase, label]) => {
2684
+ const duration = timings[phase];
2685
+ return duration === undefined ? null : `${label} ${getDurationString(duration)}`;
2686
+ }).filter((value) => value !== null).join(", ");
2687
+ var prepareAbsoluteIosDevProject = async (config, options) => {
2688
+ if (detectAbsoluteMobileHost() !== "macos")
2689
+ throw new Error("iOS simulation requires macOS and Xcode.");
2690
+ const projectRoot = resolve5(options.projectRoot);
2691
+ const checks = await inspectAbsoluteMobileToolchain({ host: "macos" });
2692
+ const failed = checks.filter((check) => check.platform === "ios" && (check.status === "fail" || check.status === "warn"));
2693
+ if (failed.length > 0)
2694
+ throw new Error(`iOS simulation is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
2695
+ const xcrun = checks.find((check) => check.id === "ios.xcrun")?.path;
2696
+ const xcodebuild = checks.find((check) => check.id === "ios.xcodebuild")?.path;
2697
+ if (!xcrun || !xcodebuild)
2698
+ throw new Error("Xcode tools disappeared after readiness checks.");
2699
+ const cap = join6(projectRoot, "node_modules", ".bin", "cap");
2700
+ if (!await pathExists5(cap))
2701
+ throw new Error("Capacitor CLI is not installed. Run absolute mobile init first.");
2702
+ await writeAbsoluteCapacitorConfig(config, { projectRoot });
2703
+ await mkdir5(config.bundleDirectory, { recursive: true });
2704
+ const placeholder = join6(config.bundleDirectory, "index.html");
2705
+ if (!await pathExists5(placeholder))
2706
+ await writeFile6(placeholder, `<!doctype html><title>AbsoluteJS mobile development</title>
2707
+ `);
2708
+ const nativeDirectory = join6(config.nativeProjectDirectory, "ios");
2709
+ if (!await pathExists5(nativeDirectory)) {
2710
+ if (!options.createNativeProject)
2711
+ throw new Error("iOS native project has not been created.");
2712
+ const run = options.run ?? defaultRun2;
2713
+ if (await run([cap, "add", "ios"], { cwd: projectRoot }) !== 0)
2714
+ throw new Error("Capacitor iOS project creation failed.");
2715
+ }
2716
+ return {
2717
+ cap,
2718
+ config,
2719
+ nativeDirectory,
2720
+ projectRoot,
2721
+ xcodebuild,
2722
+ xcrun
2723
+ };
2724
+ };
2725
+ var startAbsoluteIosDevSession = async (options) => {
2726
+ const { project } = options;
2727
+ const capture = options.capture ?? defaultCapture3;
2728
+ const run = options.run ?? defaultRun2;
2729
+ const sleep = options.sleep ?? Bun.sleep;
2730
+ const spawn = options.spawn ?? defaultSpawn;
2731
+ const log = options.log ?? console.log;
2732
+ const startedAt = performance.now();
2733
+ let phaseStartedAt = performance.now();
2734
+ const timings = {};
2735
+ let state = "syncing";
2736
+ const transition = (next) => {
2737
+ if (next === state) {
2738
+ options.onStateChange?.(next);
2739
+ return;
2740
+ }
2741
+ const now = performance.now();
2742
+ const durationMs = now - phaseStartedAt;
2743
+ timings[state] = (timings[state] ?? 0) + durationMs;
2744
+ options.onPhaseTiming?.({
2745
+ durationMs,
2746
+ phase: state,
2747
+ totalMs: now - startedAt
2748
+ });
2749
+ state = next;
2750
+ phaseStartedAt = now;
2751
+ options.onStateChange?.(next);
2752
+ };
2753
+ let nativeLogs = null;
2754
+ const closeLogs = async () => {
2755
+ const stream = nativeLogs;
2756
+ nativeLogs = null;
2757
+ await stream?.close().catch(() => {
2758
+ return;
2759
+ });
2760
+ };
2761
+ try {
2762
+ await repairAbsoluteIosDevSession(project.projectRoot);
2763
+ throwIfAborted2(options.signal);
2764
+ transition("syncing");
2765
+ await requireSuccess2([project.cap, "sync", "ios"], "Capacitor iOS synchronization", run, { cwd: project.projectRoot, signal: options.signal });
2766
+ transition("configuring");
2767
+ await writeDevProjection(project, options.port, options.https === true);
2768
+ throwIfAborted2(options.signal);
2769
+ const fingerprintStartedAt = performance.now();
2770
+ const fingerprintPromise = fingerprintAbsoluteIosDevProject(project).then((fingerprint2) => {
2771
+ timings.fingerprinting = performance.now() - fingerprintStartedAt;
2772
+ return fingerprint2;
2773
+ });
2774
+ transition("booting");
2775
+ const { created, device } = await ensureManagedSimulator(project, capture);
2776
+ const startedSimulator = created || device.state !== "Booted";
2777
+ bootSimulator(project, device, capture);
2778
+ spawn([
2779
+ "open",
2780
+ "-a",
2781
+ "Simulator",
2782
+ "--args",
2783
+ "-CurrentDeviceUDID",
2784
+ device.udid
2785
+ ]);
2786
+ transition("connecting");
2787
+ await waitForBootedSimulator(project, device.udid, capture, sleep, options.signal);
2788
+ await requireSuccess2([project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", run, { signal: options.signal });
2789
+ const fingerprint = await fingerprintPromise;
2790
+ transition("checking-native");
2791
+ const nativeCacheHit = await ensureIosDebugApp({
2792
+ cache: await readNativeCache(project.projectRoot),
2793
+ capture,
2794
+ fingerprint,
2795
+ log,
2796
+ project,
2797
+ run,
2798
+ signal: options.signal,
2799
+ transition,
2800
+ udid: device.udid
2801
+ });
2802
+ throwIfAborted2(options.signal);
2803
+ if (options.nativeLog)
2804
+ transition("streaming-logs");
2805
+ nativeLogs = attachNativeLogs(project, device.udid, options);
2806
+ transition("launching");
2807
+ await requireSuccess2([
2808
+ project.xcrun,
2809
+ "simctl",
2810
+ "launch",
2811
+ "--terminate-running-process",
2812
+ device.udid,
2813
+ project.config.appId
2814
+ ], "iOS app launch", run, { signal: options.signal });
2815
+ transition("ready");
2816
+ timings.total = performance.now() - startedAt;
2817
+ log(`iOS simulator connected (${device.udid}) with HMR on port ${options.port} in ${getDurationString(timings.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
2818
+ log(`iOS startup: ${timingSummary(timings)}.`);
2819
+ let closed = false;
2820
+ const close = async () => {
2821
+ if (closed)
2822
+ return;
2823
+ closed = true;
2824
+ transition("closing");
2825
+ await closeLogs();
2826
+ await repairAbsoluteIosDevSession(project.projectRoot);
2827
+ transition("closed");
2828
+ };
2829
+ return {
2830
+ close,
2831
+ nativeCacheHit,
2832
+ startedSimulator,
2833
+ timings: { ...timings },
2834
+ udid: device.udid,
2835
+ rebuild: async () => {
2836
+ if (closed)
2837
+ throw new Error("iOS development session is closed.");
2838
+ log("iOS native inputs changed; rebuilding without restarting the dev server.");
2839
+ await close();
2840
+ return startAbsoluteIosDevSession(options);
2841
+ },
2842
+ relaunch: async () => {
2843
+ if (closed)
2844
+ throw new Error("iOS development session is closed.");
2845
+ transition("launching");
2846
+ try {
2847
+ await requireSuccess2([
2848
+ project.xcrun,
2849
+ "simctl",
2850
+ "launch",
2851
+ "--terminate-running-process",
2852
+ device.udid,
2853
+ project.config.appId
2854
+ ], "iOS app relaunch", run, { signal: options.signal });
2855
+ transition("ready");
2856
+ log(`iOS app relaunched on ${device.udid}.`);
2857
+ } catch (error) {
2858
+ transition("failed");
2859
+ throw error;
2860
+ }
2861
+ },
2862
+ screenshot: async (destination) => {
2863
+ const resolved = resolve5(project.projectRoot, destination);
2864
+ if (!isInside(project.projectRoot, resolved))
2865
+ throw new Error("iOS screenshot destination must remain inside the project.");
2866
+ await mkdir5(dirname4(resolved), { recursive: true });
2867
+ await requireSuccess2([
2868
+ project.xcrun,
2869
+ "simctl",
2870
+ "io",
2871
+ device.udid,
2872
+ "screenshot",
2873
+ resolved
2874
+ ], "iOS simulator screenshot", run, { signal: options.signal });
2875
+ return resolved;
2876
+ },
2877
+ get state() {
2878
+ return state;
2879
+ }
2880
+ };
2881
+ } catch (error) {
2882
+ transition("failed");
2883
+ await closeLogs();
2884
+ await repairAbsoluteIosDevSession(project.projectRoot);
2885
+ throw error;
2886
+ }
2887
+ };
2888
+ // src/mobile/iosNativeWatcher.ts
2889
+ import { watch } from "fs";
2890
+ import { basename } from "path";
2891
+ var NATIVE_CHANGE_DEBOUNCE_MS = 500;
2892
+ var ROOT_NATIVE_INPUTS = new Set([
2893
+ "absolute.config.js",
2894
+ "absolute.config.mjs",
2895
+ "absolute.config.ts",
2896
+ "absolutejs.config.js",
2897
+ "absolutejs.config.mjs",
2898
+ "absolutejs.config.ts",
2899
+ "bun.lock",
2900
+ "bun.lockb",
2901
+ "capacitor.config.js",
2902
+ "capacitor.config.ts",
2903
+ "package.json"
2904
+ ]);
2905
+ var createAbsoluteIosNativeWatcher = async (options) => {
2906
+ let fingerprint = await fingerprintAbsoluteIosDevProject(options.project);
2907
+ let closed = false;
2908
+ let running = false;
2909
+ let timer;
2910
+ let rootInputChanged = false;
2911
+ const changedPaths = new Set;
2912
+ const watchers = [];
2913
+ const debounceMs = options.debounceMs ?? NATIVE_CHANGE_DEBOUNCE_MS;
2914
+ const close = () => {
2915
+ if (closed)
2916
+ return;
2917
+ closed = true;
2918
+ if (timer)
2919
+ clearTimeout(timer);
2920
+ watchers.forEach((watcher) => watcher.close());
2921
+ options.signal?.removeEventListener("abort", close);
2922
+ };
2923
+ const schedule = () => {
2924
+ if (closed || running)
2925
+ return;
2926
+ if (timer)
2927
+ clearTimeout(timer);
2928
+ timer = setTimeout(() => void flush(), debounceMs);
2929
+ };
2930
+ const flush = async () => {
2931
+ timer = undefined;
2932
+ if (closed || running || changedPaths.size === 0)
2933
+ return;
2934
+ running = true;
2935
+ const paths = [...changedPaths].sort();
2936
+ const forced = rootInputChanged;
2937
+ changedPaths.clear();
2938
+ rootInputChanged = false;
2939
+ try {
2940
+ const next = await fingerprintAbsoluteIosDevProject(options.project);
2941
+ if (!forced && next === fingerprint)
2942
+ return;
2943
+ await options.onChange({
2944
+ afterFingerprint: next,
2945
+ beforeFingerprint: fingerprint,
2946
+ paths,
2947
+ rootInputChanged: forced
2948
+ });
2949
+ fingerprint = await fingerprintAbsoluteIosDevProject(options.project);
2950
+ } catch (error) {
2951
+ options.onError?.(error);
2952
+ } finally {
2953
+ running = false;
2954
+ if (changedPaths.size > 0)
2955
+ schedule();
2956
+ }
2957
+ };
2958
+ const record = (path, force) => {
2959
+ if (closed)
2960
+ return;
2961
+ changedPaths.add(path);
2962
+ rootInputChanged ||= force;
2963
+ schedule();
2964
+ };
2965
+ watchers.push(watch(options.project.nativeDirectory, { recursive: true }, (_event, filename) => {
2966
+ if (filename)
2967
+ record(String(filename), false);
2968
+ }));
2969
+ watchers.push(watch(options.project.projectRoot, (_event, filename) => {
2970
+ if (!filename)
2971
+ return;
2972
+ const path = String(filename);
2973
+ if (isAbsoluteIosNativeRootInput(path))
2974
+ record(path, true);
2975
+ }));
2976
+ watchers.forEach((watcher) => watcher.on("error", (error) => options.onError?.(error)));
2977
+ options.signal?.addEventListener("abort", close, { once: true });
2978
+ return { close };
2979
+ };
2980
+ var isAbsoluteIosNativeRootInput = (path) => ROOT_NATIVE_INPUTS.has(basename(path));
2981
+ // src/mobile/remoteMacProtocol.ts
2982
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
2983
+ import { chmod, mkdir as mkdir6, readFile as readFile8, rename as rename7, writeFile as writeFile7 } from "fs/promises";
2984
+ import { homedir as homedir2 } from "os";
2985
+ import {
2986
+ dirname as dirname5,
2987
+ isAbsolute as isAbsolute5,
2988
+ join as join7,
2989
+ posix,
2990
+ relative as relative6,
2991
+ resolve as resolvePath2,
2992
+ sep as sep5
2993
+ } from "path";
2994
+
2995
+ // src/mobile/remoteMacWire.ts
2996
+ var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t";
2997
+ var ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
2998
+
2999
+ // src/mobile/remoteMacProtocol.ts
3000
+ var PROFILE_FORMAT = 1;
3001
+ var REMOTE_STDIN_FLUSH_ATTEMPTS = 3;
3002
+ var PROFILE_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
3003
+ var SSH_DESTINATION = /^(?:[A-Za-z0-9._-]+@)?[A-Za-z0-9._:-]+$/u;
3004
+ var defaultProfilePath = () => join7(homedir2(), ".absolutejs", "mobile", "remote-macs.json");
3005
+ var emptyStore = () => ({
3006
+ format: PROFILE_FORMAT,
3007
+ profiles: {}
3008
+ });
3009
+ var loadStore = async (path = defaultProfilePath()) => {
3010
+ try {
3011
+ const parsed = JSON.parse(await readFile8(path, "utf8"));
3012
+ if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
3013
+ throw new Error("Unsupported remote Mac profile format.");
3014
+ for (const [key, profile] of Object.entries(parsed.profiles)) {
3015
+ 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 "))
3016
+ throw new Error(`Remote Mac profile ${JSON.stringify(key)} is invalid.`);
3017
+ }
3018
+ if (parsed.defaultProfile !== undefined && !parsed.profiles[parsed.defaultProfile])
3019
+ throw new Error("The default remote Mac profile does not exist.");
3020
+ return parsed;
3021
+ } catch (error) {
3022
+ if (error.code === "ENOENT")
3023
+ return emptyStore();
3024
+ throw error;
3025
+ }
3026
+ };
3027
+ var saveStore = async (store, path = defaultProfilePath()) => {
3028
+ await mkdir6(dirname5(path), { recursive: true });
3029
+ const temporary = `${path}.${randomUUID3()}.tmp`;
3030
+ await writeFile7(temporary, `${JSON.stringify(store, null, 2)}
3031
+ `, {
3032
+ mode: 384
3033
+ });
3034
+ await rename7(temporary, path);
3035
+ await chmod(path, 384);
3036
+ };
3037
+ var validateAbsoluteRemoteMacProfileName = (name) => {
3038
+ const normalized = name.trim().toLowerCase();
3039
+ if (!PROFILE_NAME.test(normalized))
3040
+ throw new TypeError("Remote Mac profile names must use 1-64 lowercase letters, digits, dots, dashes, or underscores.");
3041
+ return normalized;
3042
+ };
3043
+ var validateAbsoluteSshDestination = (destination) => {
3044
+ const normalized = destination.trim();
3045
+ if (!SSH_DESTINATION.test(normalized) || normalized.startsWith("-"))
3046
+ throw new TypeError("Remote Mac SSH destination must be a host, SSH alias, or user@host without command-line options.");
3047
+ return normalized;
3048
+ };
3049
+ var validatePort = (port) => {
3050
+ if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535))
3051
+ throw new TypeError("Remote Mac SSH port must be between 1 and 65535.");
3052
+ return port;
3053
+ };
3054
+ var shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
3055
+ var absoluteRemoteMacSshBase = (profile, options = {}) => [
3056
+ "ssh",
3057
+ "-o",
3058
+ "BatchMode=yes",
3059
+ "-o",
3060
+ "ConnectTimeout=10",
3061
+ "-o",
3062
+ "ServerAliveInterval=15",
3063
+ "-o",
3064
+ "ServerAliveCountMax=3",
3065
+ "-o",
3066
+ `StrictHostKeyChecking=${options.acceptNew ? "accept-new" : "yes"}`,
3067
+ ...profile.port ? ["-p", String(profile.port)] : [],
3068
+ profile.destination
3069
+ ];
3070
+ var localCapture = async (command) => {
3071
+ const process2 = Bun.spawn(command, {
3072
+ stderr: "pipe",
3073
+ stdin: "ignore",
3074
+ stdout: "pipe"
3075
+ });
3076
+ const [exitCode, stdout, stderr] = await Promise.all([
3077
+ process2.exited,
3078
+ new Response(process2.stdout).text(),
3079
+ new Response(process2.stderr).text()
3080
+ ]);
3081
+ return { exitCode, stderr, stdout };
3082
+ };
3083
+ var defaultTransport = {
3084
+ capture: localCapture,
3085
+ spawn: (command, options) => Bun.spawn(command, {
3086
+ signal: options.signal,
3087
+ stderr: "pipe",
3088
+ stdin: "pipe",
3089
+ stdout: "pipe"
3090
+ })
3091
+ };
3092
+ var requireRemoteSuccess = (result, label) => {
3093
+ if (result.exitCode !== 0)
3094
+ throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim() || `status ${result.exitCode}`}`);
3095
+ return result.stdout.trim();
3096
+ };
3097
+ var getAbsoluteRemoteMacProfile = async (name, profilePath) => {
3098
+ const store = await loadStore(profilePath);
3099
+ const selected = name ?? process.env.ABSOLUTE_IOS_REMOTE ?? store.defaultProfile;
3100
+ if (!selected)
3101
+ return;
3102
+ const profile = store.profiles[selected];
3103
+ if (!profile)
3104
+ throw new Error(`Remote Mac profile ${JSON.stringify(selected)} was not found.`);
3105
+ return profile;
3106
+ };
3107
+ var inspectAbsoluteRemoteMac = async (destination, options = {}) => {
3108
+ const profile = {
3109
+ destination: validateAbsoluteSshDestination(destination),
3110
+ port: validatePort(options.port)
3111
+ };
3112
+ const capture = options.transport?.capture ?? defaultTransport.capture;
3113
+ const command = [
3114
+ ...absoluteRemoteMacSshBase(profile, {
3115
+ acceptNew: options.acceptNew === true
3116
+ }),
3117
+ "/bin/sh -lc",
3118
+ 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)"`)
3119
+ ];
3120
+ const lines = requireRemoteSuccess(await capture(command), "Remote Mac handshake").split(/\r?\n/u);
3121
+ const [operatingSystem, home, bunPath, xcodeVersion] = lines;
3122
+ if (operatingSystem !== "Darwin")
3123
+ throw new Error("The SSH target is not a Mac.");
3124
+ if (!home?.startsWith("/") || !bunPath?.startsWith("/"))
3125
+ throw new Error("The remote Mac must have Bun installed and available to SSH.");
3126
+ if (!xcodeVersion?.startsWith("Xcode "))
3127
+ throw new Error("The remote Mac must have full Xcode installed and selected.");
3128
+ return { bunPath, home, os: operatingSystem, xcodeVersion };
3129
+ };
3130
+ var listAbsoluteRemoteMacProfiles = async (profilePath) => {
3131
+ const store = await loadStore(profilePath);
3132
+ return {
3133
+ defaultProfile: store.defaultProfile,
3134
+ profiles: Object.values(store.profiles).sort((left, right) => left.name.localeCompare(right.name))
3135
+ };
3136
+ };
3137
+ var pairAbsoluteRemoteMac = async (options) => {
3138
+ const name = validateAbsoluteRemoteMacProfileName(options.name);
3139
+ const destination = validateAbsoluteSshDestination(options.destination);
3140
+ const port = validatePort(options.port);
3141
+ const inspection = await inspectAbsoluteRemoteMac(destination, {
3142
+ acceptNew: true,
3143
+ port,
3144
+ transport: options.transport
3145
+ });
3146
+ const workspaceRoot = options.workspaceRoot ? options.workspaceRoot.trim() : posix.join(inspection.home, ".absolutejs", "remote-ios");
3147
+ if (!workspaceRoot.startsWith("/") || workspaceRoot === "/" || /[\r\n\0]/u.test(workspaceRoot))
3148
+ throw new TypeError("Remote Mac workspace must be an absolute macOS path.");
3149
+ const profile = {
3150
+ bunPath: inspection.bunPath,
3151
+ createdAt: new Date().toISOString(),
3152
+ destination,
3153
+ name,
3154
+ ...port ? { port } : {},
3155
+ workspaceRoot,
3156
+ xcodeVersion: inspection.xcodeVersion
3157
+ };
3158
+ const store = await loadStore(options.profilePath);
3159
+ store.profiles[name] = profile;
3160
+ store.defaultProfile = name;
3161
+ await saveStore(store, options.profilePath);
3162
+ return profile;
3163
+ };
3164
+ var removeAbsoluteRemoteMacProfile = async (name, profilePath) => {
3165
+ const normalized = validateAbsoluteRemoteMacProfileName(name);
3166
+ const store = await loadStore(profilePath);
3167
+ if (!store.profiles[normalized])
3168
+ return false;
3169
+ delete store.profiles[normalized];
3170
+ if (store.defaultProfile === normalized) {
3171
+ const [nextDefault] = Object.keys(store.profiles).sort();
3172
+ store.defaultProfile = nextDefault;
3173
+ }
3174
+ await saveStore(store, profilePath);
3175
+ return true;
3176
+ };
3177
+ var projectIdentity = (projectRoot, appId) => createHash7("sha256").update(`${resolvePath2(projectRoot)}\x00${appId}`).digest("hex").slice(0, 20);
3178
+ var createAbsoluteRemoteIosDevProject = (config, projectRoot, profile) => ({
3179
+ cap: join7(resolvePath2(projectRoot), "node_modules", ".bin", "cap"),
3180
+ config,
3181
+ nativeDirectory: join7(config.nativeProjectDirectory, "ios"),
3182
+ profile,
3183
+ projectRoot: resolvePath2(projectRoot),
3184
+ remote: true,
3185
+ remoteProjectRoot: posix.join(profile.workspaceRoot, "projects", projectIdentity(projectRoot, config.appId), "current"),
3186
+ xcodebuild: "remote:xcodebuild",
3187
+ xcrun: "remote:xcrun"
3188
+ });
3189
+ var installAbsoluteRemoteMacAgent = async (project) => {
3190
+ const artifact = await materializeAbsoluteRemoteMacAgent(project.projectRoot);
3191
+ const directory = posix.join(project.profile.workspaceRoot, "agents", `protocol-${ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION}`, artifact.sha256);
3192
+ const remotePath = posix.join(directory, "agent.js");
3193
+ const verifyScript = `test -f ${shellQuote(remotePath)} && ` + `test "$(shasum -a 256 ${shellQuote(remotePath)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`;
3194
+ const verified = await defaultTransport.capture([
3195
+ ...absoluteRemoteMacSshBase(project.profile),
3196
+ "/bin/sh -lc",
3197
+ shellQuote(verifyScript)
3198
+ ]);
3199
+ if (verified.exitCode === 0)
3200
+ return { ...artifact, remotePath, uploaded: false };
3201
+ const temporary = posix.join(directory, `.agent-${randomUUID3()}.tmp`);
3202
+ const installScript = [
3203
+ "set -eu",
3204
+ "umask 077",
3205
+ `mkdir -p ${shellQuote(directory)}`,
3206
+ `cat > ${shellQuote(temporary)}`,
3207
+ `test "$(shasum -a 256 ${shellQuote(temporary)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`,
3208
+ `chmod 600 ${shellQuote(temporary)}`,
3209
+ `mv ${shellQuote(temporary)} ${shellQuote(remotePath)}`
3210
+ ].join("; ");
3211
+ const upload = Bun.spawn([
3212
+ ...absoluteRemoteMacSshBase(project.profile),
3213
+ "/bin/sh -lc",
3214
+ shellQuote(installScript)
3215
+ ], {
3216
+ stderr: "pipe",
3217
+ stdin: Bun.file(artifact.path),
3218
+ stdout: "pipe"
3219
+ });
3220
+ const [exitCode, stderr] = await Promise.all([
3221
+ upload.exited,
3222
+ new Response(upload.stderr).text()
3223
+ ]);
3224
+ if (exitCode !== 0)
3225
+ throw new Error(`Remote Mac agent installation failed: ${stderr.trim() || `status ${exitCode}`}`);
3226
+ return { ...artifact, remotePath, uploaded: true };
3227
+ };
3228
+ var materializeAbsoluteRemoteMacAgent = async (projectRoot) => {
3229
+ const shippedCandidates = [
3230
+ join7(import.meta.dir, "remoteMacAgentEntry.js"),
3231
+ join7(import.meta.dir, "..", "mobile", "remoteMacAgentEntry.js")
3232
+ ];
3233
+ let path;
3234
+ for (const candidate of shippedCandidates) {
3235
+ if (await Bun.file(candidate).exists()) {
3236
+ path = candidate;
3237
+ break;
3238
+ }
3239
+ }
3240
+ if (!path) {
3241
+ const sourceCandidates = [
3242
+ join7(import.meta.dir, "remoteMacAgentEntry.ts"),
3243
+ join7(import.meta.dir, "..", "..", "src", "mobile", "remoteMacAgentEntry.ts")
3244
+ ];
3245
+ const source = await sourceCandidates.reduce(async (found, candidate) => await found ?? (await Bun.file(candidate).exists() ? candidate : undefined), Promise.resolve(undefined));
3246
+ if (!source)
3247
+ throw new Error("The AbsoluteJS installation does not contain its remote Mac agent artifact.");
3248
+ const outdir = join7(resolvePath2(projectRoot), ".absolutejs", "mobile", "remote-agent");
3249
+ await mkdir6(outdir, { recursive: true });
3250
+ const result = await Bun.build({
3251
+ entrypoints: [source],
3252
+ minify: true,
3253
+ outdir,
3254
+ target: "bun"
3255
+ });
3256
+ if (!result.success)
3257
+ throw new AggregateError(result.logs, "Failed to build the AbsoluteJS remote Mac agent.");
3258
+ path = join7(outdir, "remoteMacAgentEntry.js");
3259
+ }
3260
+ const bytes = await Bun.file(path).arrayBuffer();
3261
+ const sha256 = createHash7("sha256").update(new Uint8Array(bytes)).digest("hex");
3262
+ return { bytes: bytes.byteLength, path, sha256 };
3263
+ };
3264
+ var portableRelativePath = (root, path) => relative6(root, path).split(sep5).join(posix.sep);
3265
+ var portableMobileConfig = (project) => ({
3266
+ appId: project.config.appId,
3267
+ appName: project.config.appName,
3268
+ bundleDirectory: portableRelativePath(project.projectRoot, project.config.bundleDirectory),
3269
+ ...project.config.deepLinkScheme || project.config.deepLinkHosts.length > 1 || project.config.appleAppIdPrefix ? {
3270
+ deepLinks: {
3271
+ ...project.config.deepLinkScheme ? { scheme: project.config.deepLinkScheme } : {},
3272
+ hosts: project.config.deepLinkHosts,
3273
+ ...project.config.appleAppIdPrefix ? {
3274
+ apple: {
3275
+ appIdPrefix: project.config.appleAppIdPrefix
3276
+ }
3277
+ } : {}
3278
+ }
3279
+ } : {},
3280
+ entry: project.config.entry,
3281
+ ...project.config.iosVersion ? { ios: { version: project.config.iosVersion } } : {},
3282
+ nativeProject: {
3283
+ directory: portableRelativePath(project.projectRoot, project.config.nativeProjectDirectory),
3284
+ mode: "source"
3285
+ },
3286
+ platforms: ["ios"],
3287
+ server: { productionOrigin: project.config.productionOrigin }
3288
+ });
3289
+ var absoluteRemoteProjectSyncCommands = (project) => {
3290
+ const current = project.remoteProjectRoot;
3291
+ const parent = posix.dirname(current);
3292
+ const staging = posix.join(parent, `.incoming-${randomUUID3()}`);
3293
+ const previous = posix.join(parent, ".previous");
3294
+ const script = [
3295
+ "set -eu",
3296
+ `mkdir -p ${shellQuote(staging)}`,
3297
+ `tar -xf - -C ${shellQuote(staging)}`,
3298
+ `if [ -d ${shellQuote(posix.join(current, "node_modules"))} ]; then mv ${shellQuote(posix.join(current, "node_modules"))} ${shellQuote(posix.join(staging, "node_modules"))}; fi`,
3299
+ `if [ -d ${shellQuote(posix.join(current, ".absolutejs"))} ]; then mv ${shellQuote(posix.join(current, ".absolutejs"))} ${shellQuote(posix.join(staging, ".absolutejs"))}; fi`,
3300
+ `rm -rf ${shellQuote(previous)}`,
3301
+ `if [ -d ${shellQuote(current)} ]; then mv ${shellQuote(current)} ${shellQuote(previous)}; fi`,
3302
+ `mv ${shellQuote(staging)} ${shellQuote(current)}`,
3303
+ `rm -rf ${shellQuote(previous)}`
3304
+ ].join("; ");
3305
+ return {
3306
+ remote: [
3307
+ ...absoluteRemoteMacSshBase(project.profile),
3308
+ "/bin/sh -lc",
3309
+ shellQuote(script)
3310
+ ],
3311
+ tar: [
3312
+ "tar",
3313
+ "--exclude=.git",
3314
+ "--exclude=node_modules",
3315
+ "--exclude=build",
3316
+ "--exclude=.absolutejs",
3317
+ "-cf",
3318
+ "-",
3319
+ "-C",
3320
+ project.projectRoot,
3321
+ "."
3322
+ ]
3323
+ };
3324
+ };
3325
+ var syncAbsoluteRemoteMacProject = async (project) => {
3326
+ const commands = absoluteRemoteProjectSyncCommands(project);
3327
+ const archive = Bun.spawn(commands.tar, {
3328
+ stderr: "pipe",
3329
+ stdout: "pipe"
3330
+ });
3331
+ const upload = Bun.spawn(commands.remote, {
3332
+ stderr: "pipe",
3333
+ stdin: archive.stdout,
3334
+ stdout: "pipe"
3335
+ });
3336
+ const [archiveExit, uploadExit, archiveError, uploadError] = await Promise.all([
3337
+ archive.exited,
3338
+ upload.exited,
3339
+ new Response(archive.stderr).text(),
3340
+ new Response(upload.stderr).text()
3341
+ ]);
3342
+ if (archiveExit !== 0 || uploadExit !== 0)
3343
+ throw new Error(`Remote Mac project synchronization failed: ${(archiveError || uploadError).trim()}`);
3344
+ const install = await defaultTransport.capture([
3345
+ ...absoluteRemoteMacSshBase(project.profile),
3346
+ "/bin/sh -lc",
3347
+ shellQuote(`cd ${shellQuote(project.remoteProjectRoot)} && ${shellQuote(project.profile.bunPath)} install --frozen-lockfile`)
3348
+ ]);
3349
+ requireRemoteSuccess(install, "Remote Mac dependency installation");
3350
+ };
3351
+ var consumeLines2 = async (stream, onLine) => {
3352
+ const reader = stream.getReader();
3353
+ const decoder = new TextDecoder;
3354
+ let buffered = "";
3355
+ try {
3356
+ while (true) {
3357
+ const { done, value } = await reader.read();
3358
+ if (done)
3359
+ break;
3360
+ buffered += decoder.decode(value, { stream: true });
3361
+ const lines = buffered.split(/\r?\n/u);
3362
+ buffered = lines.pop() ?? "";
3363
+ lines.forEach(onLine);
3364
+ }
3365
+ buffered += decoder.decode();
3366
+ if (buffered)
3367
+ onLine(buffered);
3368
+ } finally {
3369
+ reader.releaseLock();
3370
+ }
3371
+ };
3372
+ var startAbsoluteRemoteIosDevSession = async (options) => {
3373
+ const startedAt = performance.now();
3374
+ const transport = options.transport ?? defaultTransport;
3375
+ const installAgent = options.installAgent ?? installAbsoluteRemoteMacAgent;
3376
+ const syncProject = options.syncProject ?? syncAbsoluteRemoteMacProject;
3377
+ const agentStartedAt = performance.now();
3378
+ const agent = await installAgent(options.project);
3379
+ const agentDuration = performance.now() - agentStartedAt;
3380
+ const syncStartedAt = performance.now();
3381
+ await syncProject(options.project);
3382
+ const syncDuration = performance.now() - syncStartedAt;
3383
+ const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
3384
+ const remoteCommand = [
3385
+ `cd ${shellQuote(options.project.remoteProjectRoot)}`,
3386
+ "&&",
3387
+ "exec",
3388
+ shellQuote(options.project.profile.bunPath),
3389
+ shellQuote(agent.remotePath),
3390
+ "--port",
3391
+ String(options.port),
3392
+ "--mobile-config",
3393
+ shellQuote(encodedConfig),
3394
+ ...options.https ? ["--https"] : []
3395
+ ].join(" ");
3396
+ const command = [
3397
+ ...absoluteRemoteMacSshBase(options.project.profile),
3398
+ "-o",
3399
+ "ExitOnForwardFailure=yes",
3400
+ "-R",
3401
+ `${options.port}:127.0.0.1:${options.port}`,
3402
+ "/bin/sh -lc",
3403
+ shellQuote(remoteCommand)
3404
+ ];
3405
+ const connectStartedAt = performance.now();
3406
+ const process2 = transport.spawn(command, { signal: options.signal });
3407
+ let state = "syncing";
3408
+ let ready;
3409
+ let fatal;
3410
+ const pending = new Map;
3411
+ let resolveReady;
3412
+ let rejectReady;
3413
+ const readyPromise = new Promise((resolve6, reject) => {
3414
+ resolveReady = resolve6;
3415
+ rejectReady = reject;
3416
+ });
3417
+ const handleEvent = (event) => {
3418
+ if (event.v !== ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION) {
3419
+ rejectReady(new Error("Remote Mac protocol version mismatch."));
3420
+ return;
3421
+ }
3422
+ if (event.type === "log")
3423
+ options.log?.(event.message);
3424
+ if (event.type === "native-log")
3425
+ options.nativeLog?.(event.entry);
3426
+ if (event.type === "state") {
3427
+ ({ state } = event);
3428
+ options.onStateChange?.(state);
3429
+ }
3430
+ if (event.type === "timing")
3431
+ options.onPhaseTiming?.(event);
3432
+ if (event.type === "ready") {
3433
+ ready = event;
3434
+ resolveReady();
3435
+ }
3436
+ if (event.type === "fatal") {
3437
+ fatal = new Error(event.error);
3438
+ rejectReady(fatal);
3439
+ }
3440
+ if (event.type === "response") {
3441
+ const request2 = pending.get(event.id);
3442
+ if (!request2)
3443
+ return;
3444
+ pending.delete(event.id);
3445
+ if (event.ok)
3446
+ request2.resolve(event.result);
3447
+ else
3448
+ request2.reject(new Error(event.error ?? "Remote command failed."));
3449
+ }
3450
+ };
3451
+ const stdoutDone = consumeLines2(process2.stdout, (line) => {
3452
+ if (!line.startsWith(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX))
3453
+ return;
3454
+ try {
3455
+ handleEvent(JSON.parse(line.slice(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX.length)));
3456
+ } catch {
3457
+ options.log?.(`Remote Mac emitted an invalid protocol event.`);
3458
+ }
3459
+ }).catch((error) => {
3460
+ fatal = error instanceof Error ? error : new Error("Failed to read the remote Mac protocol stream.");
3461
+ rejectReady(fatal);
3462
+ });
3463
+ 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."}`));
3464
+ process2.exited.then(async (exitCode) => {
3465
+ await Promise.all([stdoutDone, stderrDone]);
3466
+ const error = fatal ?? new Error(`Remote Mac connection closed with status ${exitCode}.`);
3467
+ if (!ready)
3468
+ rejectReady(error);
3469
+ pending.forEach(({ reject }) => reject(error));
3470
+ pending.clear();
3471
+ return;
3472
+ });
3473
+ await readyPromise;
3474
+ if (!ready)
3475
+ throw fatal ?? new Error("Remote Mac did not become ready.");
3476
+ const totalDuration = performance.now() - startedAt;
3477
+ let currentReady = {
3478
+ ...ready,
3479
+ timings: {
3480
+ ...ready.timings,
3481
+ "remote-agent": agentDuration,
3482
+ "remote-connect": performance.now() - connectStartedAt,
3483
+ "remote-sync": syncDuration,
3484
+ total: totalDuration
3485
+ }
3486
+ };
3487
+ 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.`);
3488
+ const request = (commandName) => {
3489
+ const id = randomUUID3();
3490
+ const response = new Promise((resolve6, reject) => pending.set(id, { reject, resolve: resolve6 }));
3491
+ process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
3492
+ `);
3493
+ const flush = async () => {
3494
+ for (let attempt = 0;attempt < REMOTE_STDIN_FLUSH_ATTEMPTS; attempt++) {
3495
+ try {
3496
+ await process2.stdin.flush();
3497
+ return;
3498
+ } catch {
3499
+ await Promise.resolve();
3500
+ }
3501
+ }
3502
+ };
3503
+ return flush().then(() => response);
3504
+ };
3505
+ let closed = false;
3506
+ const close = async () => {
3507
+ if (closed)
3508
+ return;
3509
+ closed = true;
3510
+ await request("close").catch(() => {
3511
+ return;
3512
+ });
3513
+ process2.stdin.end();
3514
+ await process2.exited.catch(() => {
3515
+ return;
3516
+ });
3517
+ };
3518
+ const makeSession = () => ({
3519
+ close,
3520
+ nativeCacheHit: currentReady.nativeCacheHit,
3521
+ startedSimulator: currentReady.startedSimulator,
3522
+ timings: currentReady.timings,
3523
+ udid: currentReady.udid,
3524
+ rebuild: async () => {
3525
+ const rebuildStartedAt = performance.now();
3526
+ const rebuildSyncStartedAt = performance.now();
3527
+ await syncProject(options.project);
3528
+ const rebuildSyncDuration = performance.now() - rebuildSyncStartedAt;
3529
+ const result = await request("rebuild");
3530
+ currentReady = {
3531
+ ...result,
3532
+ timings: {
3533
+ ...result.timings,
3534
+ "remote-sync": rebuildSyncDuration,
3535
+ total: performance.now() - rebuildStartedAt
3536
+ }
3537
+ };
3538
+ return makeSession();
3539
+ },
3540
+ relaunch: async () => {
3541
+ await request("relaunch");
3542
+ },
3543
+ screenshot: async (destination) => {
3544
+ const result = await request("screenshot");
3545
+ const target = resolvePath2(options.project.projectRoot, destination);
3546
+ const targetRelative = relative6(options.project.projectRoot, target);
3547
+ if (targetRelative.startsWith("..") || isAbsolute5(targetRelative))
3548
+ throw new Error("iOS screenshot must remain inside the project.");
3549
+ await mkdir6(dirname5(target), { recursive: true });
3550
+ await writeFile7(target, Buffer.from(result.data, "base64"));
3551
+ return target;
3552
+ },
3553
+ get state() {
3554
+ return state;
3555
+ }
3556
+ });
3557
+ return makeSession();
1463
3558
  };
1464
3559
  // src/mobile/associationFiles.ts
1465
3560
  import {
1466
- access as access5,
1467
- mkdir as mkdir5,
1468
- readFile as readFile6,
1469
- rename as rename6,
1470
- rm as rm5,
1471
- writeFile as writeFile6
3561
+ access as access7,
3562
+ mkdir as mkdir7,
3563
+ readFile as readFile9,
3564
+ rename as rename8,
3565
+ rm as rm6,
3566
+ writeFile as writeFile8
1472
3567
  } from "fs/promises";
1473
- import { resolve as resolve6 } from "path";
3568
+ import { resolve as resolve7 } from "path";
1474
3569
  import { Elysia } from "elysia";
1475
3570
 
1476
3571
  // src/mobile/config.ts
1477
- import { resolve as resolve5 } from "path";
3572
+ import { resolve as resolve6 } from "path";
1478
3573
  var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
1479
3574
  var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
1480
3575
  var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
1481
3576
  var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
1482
3577
  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])?))*$/;
1483
3578
  var resolveProjectPath = (projectRoot, value, field) => {
1484
- const root = resolve5(projectRoot);
1485
- const path = resolve5(root, value);
3579
+ const root = resolve6(projectRoot);
3580
+ const path = resolve6(root, value);
1486
3581
  if (path !== root && !path.startsWith(`${root}/`)) {
1487
3582
  throw new TypeError(`${field} must remain inside the project root.`);
1488
3583
  }
@@ -1503,8 +3598,9 @@ var normalizeEntry = (entry) => {
1503
3598
  };
1504
3599
  var normalizeProductionOrigin = (value) => {
1505
3600
  const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
1506
- if (parsed.protocol !== "https:") {
1507
- throw new TypeError("mobile.server.productionOrigin must use HTTPS in production.");
3601
+ const isLoopbackHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
3602
+ if (parsed.protocol !== "https:" && !isLoopbackHttp) {
3603
+ throw new TypeError("mobile.server.productionOrigin must use HTTPS, except for a loopback development origin.");
1508
3604
  }
1509
3605
  if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
1510
3606
  throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
@@ -1528,9 +3624,8 @@ var normalizeHosts = (hosts, productionOrigin) => {
1528
3624
  }
1529
3625
  return value;
1530
3626
  };
1531
- const normalized = new Set([
1532
- normalizeHostname(new URL(productionOrigin).hostname)
1533
- ]);
3627
+ const productionHostname = new URL(productionOrigin).hostname;
3628
+ const normalized = new Set(productionHostname === "[::1]" ? [] : [normalizeHostname(productionHostname)]);
1534
3629
  for (const host of hosts ?? []) {
1535
3630
  normalized.add(normalizeHostname(host));
1536
3631
  }
@@ -1568,7 +3663,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
1568
3663
  throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
1569
3664
  }
1570
3665
  const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
1571
- const deepLinkScheme = config.deepLinks?.scheme?.trim().toLowerCase();
3666
+ const deepLinkScheme = (config.deepLinks?.scheme ?? appId).trim().toLowerCase();
1572
3667
  if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
1573
3668
  throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
1574
3669
  }
@@ -1670,7 +3765,7 @@ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {})
1670
3765
  var writeAtomic = async (path, source) => {
1671
3766
  let current;
1672
3767
  try {
1673
- current = await readFile6(path, "utf8");
3768
+ current = await readFile9(path, "utf8");
1674
3769
  } catch (error) {
1675
3770
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
1676
3771
  throw error;
@@ -1679,23 +3774,23 @@ var writeAtomic = async (path, source) => {
1679
3774
  if (current === source)
1680
3775
  return false;
1681
3776
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
1682
- await writeFile6(temporary, source, { flag: "wx" });
1683
- await rename6(temporary, path);
3777
+ await writeFile8(temporary, source, { flag: "wx" });
3778
+ await rename8(temporary, path);
1684
3779
  return true;
1685
3780
  };
1686
3781
  var exists2 = async (path) => {
1687
3782
  try {
1688
- await access5(path);
3783
+ await access7(path);
1689
3784
  return true;
1690
3785
  } catch {
1691
3786
  return false;
1692
3787
  }
1693
3788
  };
1694
3789
  var assertOwnedOutput = async (root) => {
1695
- const path = resolve6(root, OWNERSHIP_FILE);
3790
+ const path = resolve7(root, OWNERSHIP_FILE);
1696
3791
  let ownership;
1697
3792
  try {
1698
- ownership = JSON.parse(await readFile6(path, "utf8"));
3793
+ ownership = JSON.parse(await readFile9(path, "utf8"));
1699
3794
  } catch {
1700
3795
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
1701
3796
  }
@@ -1709,22 +3804,22 @@ var publishGeneratedDirectory = async (temporary, root) => {
1709
3804
  await assertOwnedOutput(root);
1710
3805
  const backup = `${root}.${crypto.randomUUID()}.previous`;
1711
3806
  if (hasCurrent)
1712
- await rename6(root, backup);
3807
+ await rename8(root, backup);
1713
3808
  try {
1714
- await rename6(temporary, root);
3809
+ await rename8(temporary, root);
1715
3810
  } catch (error) {
1716
3811
  if (hasCurrent)
1717
- await rename6(backup, root);
3812
+ await rename8(backup, root);
1718
3813
  throw error;
1719
3814
  }
1720
3815
  if (hasCurrent)
1721
- await rm5(backup, { force: true, recursive: true });
3816
+ await rm6(backup, { force: true, recursive: true });
1722
3817
  };
1723
3818
  var materializeHost = async (root, host, files) => {
1724
- const directory = resolve6(root, host, ".well-known");
1725
- await mkdir5(directory, { recursive: true });
3819
+ const directory = resolve7(root, host, ".well-known");
3820
+ await mkdir7(directory, { recursive: true });
1726
3821
  return Promise.all(files.map(async ([name, document]) => {
1727
- const path = resolve6(directory, name);
3822
+ const path = resolve7(directory, name);
1728
3823
  await writeAtomic(path, `${JSON.stringify(document, null, 2)}
1729
3824
  `);
1730
3825
  return path;
@@ -1749,7 +3844,7 @@ var associationEndpoints = (config, documents) => config.deepLinkHosts.flatMap((
1749
3844
  return endpoints;
1750
3845
  });
1751
3846
  var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory) => {
1752
- const root = resolve6(outputDirectory);
3847
+ const root = resolve7(outputDirectory);
1753
3848
  const temporary = `${root}.${crypto.randomUUID()}.tmp`;
1754
3849
  const documents = createAbsoluteMobileAssociationDocuments(config, {
1755
3850
  requireAll: true
@@ -1760,16 +3855,16 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
1760
3855
  if (documents.apple) {
1761
3856
  files.push(["apple-app-site-association", documents.apple]);
1762
3857
  }
1763
- await mkdir5(temporary, { recursive: true });
3858
+ await mkdir7(temporary, { recursive: true });
1764
3859
  try {
1765
3860
  const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
1766
- await writeAtomic(resolve6(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
3861
+ await writeAtomic(resolve7(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
1767
3862
  `);
1768
3863
  await publishGeneratedDirectory(temporary, root);
1769
- const written = temporaryPaths.map((path) => resolve6(root, path.slice(temporary.length + 1)));
3864
+ const written = temporaryPaths.map((path) => resolve7(root, path.slice(temporary.length + 1)));
1770
3865
  return { root, written };
1771
3866
  } catch (error) {
1772
- await rm5(temporary, { force: true, recursive: true });
3867
+ await rm6(temporary, { force: true, recursive: true });
1773
3868
  throw error;
1774
3869
  }
1775
3870
  };
@@ -1814,10 +3909,10 @@ var frameworks2 = new Set([
1814
3909
  "svelte",
1815
3910
  "vue"
1816
3911
  ]);
1817
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3912
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1818
3913
  var isPageFramework2 = (value) => typeof value === "string" && frameworks2.has(value);
1819
3914
  var parseAbsoluteMobileBuildPageMetadata = (value) => {
1820
- if (!isRecord3(value))
3915
+ if (!isRecord4(value))
1821
3916
  return;
1822
3917
  if (typeof value.bundleKey !== "string" || typeof value.contract !== "string" || !isPageFramework2(value.framework) || typeof value.pageId !== "string" || typeof value.propsSchemaHash !== "string") {
1823
3918
  return;
@@ -1831,45 +3926,75 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
1831
3926
  };
1832
3927
  };
1833
3928
  // src/mobile/buildPipeline.ts
1834
- import { readFile as readFile10 } from "fs/promises";
1835
- import { join as join9, resolve as resolve9 } from "path";
3929
+ import { readFile as readFile13 } from "fs/promises";
3930
+ import { join as join14, resolve as resolve12 } from "path";
1836
3931
  import { pathToFileURL as pathToFileURL2 } from "url";
1837
3932
 
1838
3933
  // src/mobile/buildRelease.ts
1839
- import { createHash as createHash6 } from "crypto";
1840
- import { readFile as readFile7 } from "fs/promises";
1841
- import { join as join6, relative as relative5, resolve as resolve7 } from "path";
1842
- var sha256 = (bytes) => createHash6("sha256").update(bytes).digest("hex");
3934
+ import { createHash as createHash8 } from "crypto";
3935
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
3936
+ import { basename as basename2, dirname as dirname6, extname, join as join8, relative as relative7, resolve as resolve8 } from "path";
3937
+ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
3938
+ var STATIC_SCRIPT_PATTERN = /(<script\b[^>]*?\bsrc\s*=\s*["'])(\/[^"']+\.(?:js|ts))(["'][^>]*>)/giu;
3939
+ var rewriteStaticScriptPaths = (source, manifest) => source.replace(STATIC_SCRIPT_PATTERN, (match, prefix, path, suffix) => {
3940
+ if (path.endsWith("/htmx.min.js"))
3941
+ return match;
3942
+ const key = toPascal(basename2(path, extname(path)));
3943
+ const builtPath = manifest[key];
3944
+ return builtPath ? `${prefix}${builtPath}${suffix}` : match;
3945
+ });
1843
3946
  var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
1844
3947
  var resolveAssetPath = (buildDirectory, assetPath) => {
1845
- const resolvedBuildDirectory = resolve7(buildDirectory);
1846
- const resolvedAsset = resolve7(assetPath);
3948
+ const resolvedBuildDirectory = resolve8(buildDirectory);
3949
+ const resolvedAsset = resolve8(assetPath);
1847
3950
  if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
1848
3951
  return resolvedAsset;
1849
3952
  }
1850
- return join6(buildDirectory, assetPath.replace(/^\/+/, ""));
3953
+ return join8(buildDirectory, assetPath.replace(/^\/+/, ""));
1851
3954
  };
1852
3955
  var pageFor = async (metadata, manifest, buildDirectory) => {
1853
3956
  const assetPath = manifest[metadata.bundleKey];
1854
3957
  if (!assetPath) {
1855
3958
  throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
1856
3959
  }
1857
- const resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
1858
- const bytes = await readFile7(resolvedAssetPath);
1859
- const bundlePath = `/${relative5(resolve7(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
3960
+ let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
3961
+ if (metadata.framework === "html" || metadata.framework === "htmx") {
3962
+ const source = await readFile10(resolvedAssetPath, "utf8");
3963
+ const rewritten = rewriteStaticScriptPaths(source, manifest);
3964
+ const documentHash = sha256(new TextEncoder().encode(rewritten));
3965
+ resolvedAssetPath = join8(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
3966
+ await mkdir8(dirname6(resolvedAssetPath), { recursive: true });
3967
+ await writeFile9(resolvedAssetPath, rewritten);
3968
+ }
3969
+ const pageAssetKey = metadata.bundleKey.replace(/Index$/u, "");
3970
+ const styleAssetPath = [
3971
+ `${pageAssetKey}BundledCSS`,
3972
+ `${pageAssetKey}CompiledCSS`
3973
+ ].map((key) => manifest[key]).find((path) => typeof path === "string");
3974
+ const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
3975
+ const [bytes, styleBytes] = await Promise.all([
3976
+ readFile10(resolvedAssetPath),
3977
+ resolvedStylePath ? readFile10(resolvedStylePath) : undefined
3978
+ ]);
3979
+ const bundlePath = `/${relative7(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
3980
+ const styleBundlePath = resolvedStylePath ? `/${relative7(resolve8(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
1860
3981
  return {
1861
3982
  bundleHash: sha256(bytes),
1862
3983
  bundlePath,
1863
3984
  contract: metadata.contract,
1864
3985
  framework: metadata.framework,
1865
3986
  pageId: metadata.pageId,
1866
- propsSchemaHash: metadata.propsSchemaHash
3987
+ propsSchemaHash: metadata.propsSchemaHash,
3988
+ ...styleBytes && styleBundlePath ? {
3989
+ styleBundleHash: sha256(styleBytes),
3990
+ styleBundlePath
3991
+ } : {}
1867
3992
  };
1868
3993
  };
1869
3994
  var buildAbsoluteMobileCompatibilityRelease = async (options) => {
1870
3995
  const [captured, producerBytes] = await Promise.all([
1871
3996
  captureAbsoluteMobileRouteGraph(options.app),
1872
- readFile7(options.producerPath)
3997
+ readFile10(options.producerPath)
1873
3998
  ]);
1874
3999
  if (captured.length === 0) {
1875
4000
  throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
@@ -1885,11 +4010,19 @@ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
1885
4010
  const pages = await Promise.all([...metadataByPage.values()].map((metadata) => pageFor(metadata, options.manifest, options.buildDirectory)));
1886
4011
  const producerHash = sha256(producerBytes);
1887
4012
  const appBuild = `ambuild_${sha256(new TextEncoder().encode(JSON.stringify({
1888
- pages: pages.map(({ bundleHash, bundlePath, contract, pageId }) => ({
4013
+ pages: pages.map(({
4014
+ bundleHash,
4015
+ bundlePath,
4016
+ contract,
4017
+ pageId,
4018
+ styleBundleHash,
4019
+ styleBundlePath
4020
+ }) => ({
1889
4021
  bundleHash,
1890
4022
  bundlePath,
1891
4023
  contract,
1892
- pageId
4024
+ pageId,
4025
+ ...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
1893
4026
  })),
1894
4027
  producerHash,
1895
4028
  runtime: options.runtime
@@ -1949,16 +4082,17 @@ var captureAbsoluteMobileRouteGraph = async (app) => {
1949
4082
 
1950
4083
  // src/mobile/capacitorBundle.ts
1951
4084
  import {
1952
- copyFile as copyFile4,
1953
- mkdir as mkdir6,
4085
+ cp,
4086
+ copyFile as copyFile5,
4087
+ mkdir as mkdir9,
1954
4088
  mkdtemp as mkdtemp4,
1955
- readFile as readFile8,
1956
- rename as rename7,
1957
- rm as rm6,
1958
- writeFile as writeFile7
4089
+ readFile as readFile11,
4090
+ rename as rename9,
4091
+ rm as rm7,
4092
+ writeFile as writeFile10
1959
4093
  } from "fs/promises";
1960
4094
  import { existsSync as existsSync2 } from "fs";
1961
- import { basename, dirname as dirname4, extname, join as join7, resolve as resolve8 } from "path";
4095
+ import { basename as basename3, dirname as dirname7, extname as extname2, join as join9, relative as relative8, resolve as resolve9 } from "path";
1962
4096
 
1963
4097
  // src/mobile/routeMatcher.ts
1964
4098
  var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
@@ -2114,14 +4248,14 @@ var envelopeResponse = (response, status) => new Response(JSON.stringify({
2114
4248
  protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
2115
4249
  response
2116
4250
  }), { headers: responseHeaders(), status });
2117
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4251
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2118
4252
  var normalizeJsonValue = (value) => {
2119
4253
  const serialized = JSON.stringify(value);
2120
4254
  if (serialized === undefined) {
2121
4255
  throw new TypeError("Mobile page props must be JSON-serializable.");
2122
4256
  }
2123
4257
  const parsed = JSON.parse(serialized);
2124
- if (!isRecord4(parsed)) {
4258
+ if (!isRecord5(parsed)) {
2125
4259
  throw new TypeError("Mobile page props must serialize to an object.");
2126
4260
  }
2127
4261
  return parsed;
@@ -2231,6 +4365,13 @@ class AbsoluteMobilePageProtocolError extends Error {
2231
4365
  this.code = code;
2232
4366
  }
2233
4367
  }
4368
+ var disposeAbsoluteMobilePage = async (target = window) => {
4369
+ const dispose = target.__ABSOLUTE_PAGE_DISPOSE__;
4370
+ target.__ABSOLUTE_PAGE_DISPOSE__ = undefined;
4371
+ target.__ABSOLUTE_PAGE_READY__ = undefined;
4372
+ if (dispose)
4373
+ await dispose();
4374
+ };
2234
4375
  var frameworks4 = new Set([
2235
4376
  "angular",
2236
4377
  "ember",
@@ -2246,14 +4387,14 @@ var upgradeReasons = new Set([
2246
4387
  "protocol",
2247
4388
  "runtime"
2248
4389
  ]);
2249
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4390
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2250
4391
  var isFramework = (value) => typeof value === "string" && frameworks4.has(value);
2251
4392
  var isUpgradeReason = (value) => typeof value === "string" && upgradeReasons.has(value);
2252
4393
  var parsePageResult = (value) => {
2253
4394
  if (typeof value.contract !== "string" || !isFramework(value.framework) || typeof value.pageId !== "string" || typeof value.status !== "number") {
2254
4395
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The mobile page response is missing required page metadata.");
2255
4396
  }
2256
- if (!isRecord5(value.props)) {
4397
+ if (!isRecord6(value.props)) {
2257
4398
  throw new AbsoluteMobilePageProtocolError("invalid-props", "The mobile page response must contain an object props value.");
2258
4399
  }
2259
4400
  return {
@@ -2286,12 +4427,17 @@ var activateAbsoluteMobilePage = async (value, options) => {
2286
4427
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "Expected a renderable mobile page response.");
2287
4428
  }
2288
4429
  const target = options.target ?? window;
4430
+ await disposeAbsoluteMobilePage(target);
2289
4431
  target.__INITIAL_PROPS__ = envelope.response.props;
4432
+ target.__ABS_ANGULAR_REQUEST_CONTEXT__ = envelope.response.props;
2290
4433
  target.__ABSOLUTE_PAGE_RENDER_MODE__ = "client";
2291
4434
  await options.loadPage({
2292
4435
  contract: envelope.response.contract,
2293
4436
  pageId: envelope.response.pageId
2294
4437
  });
4438
+ if (target.__ABSOLUTE_PAGE_READY__) {
4439
+ await target.__ABSOLUTE_PAGE_READY__;
4440
+ }
2295
4441
  return {
2296
4442
  contract: envelope.response.contract,
2297
4443
  kind: "rendered",
@@ -2299,7 +4445,7 @@ var activateAbsoluteMobilePage = async (value, options) => {
2299
4445
  };
2300
4446
  };
2301
4447
  var parseAbsoluteMobilePageEnvelope = (value) => {
2302
- if (!isRecord5(value) || !isRecord5(value.response)) {
4448
+ if (!isRecord6(value) || !isRecord6(value.response)) {
2303
4449
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The server did not return an AbsoluteJS mobile page envelope.");
2304
4450
  }
2305
4451
  if (value.protocol !== ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION) {
@@ -2384,19 +4530,51 @@ var resolveAbsoluteMobileDeepLink = (manifest, value) => {
2384
4530
  }
2385
4531
  return `${url.pathname || "/"}${url.search}${url.hash}`;
2386
4532
  };
4533
+ var resolveAbsoluteMobileNavigation = (manifest, value, localOrigin) => {
4534
+ const url = new URL(value, `${localOrigin}/`);
4535
+ const local = new URL(localOrigin);
4536
+ const production = new URL(manifest.productionOrigin);
4537
+ const matches = (candidate, allowed) => candidate.protocol === allowed.protocol && candidate.host === allowed.host;
4538
+ if (!matches(url, local) && !matches(url, production)) {
4539
+ return;
4540
+ }
4541
+ return `${url.pathname}${url.search}${url.hash}`;
4542
+ };
2387
4543
 
2388
4544
  // src/mobile/capacitorBundle.ts
2389
4545
  var MANIFEST_FILE = "absolute-mobile-manifest.json";
2390
4546
  var BOOTSTRAP_FILE = "absolute-mobile-bootstrap.js";
2391
4547
  var INDEX_FILE = "index.html";
2392
- var CLIENT_IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)["'](\/[^"']+)["']/gu;
4548
+ var CLIENT_CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?|url\(\s*)["']?((?:\/|\.\.\/|\.\/)[^"')\s]+)["']?\s*\)?/gu;
4549
+ var CLIENT_MARKUP_DEPENDENCY_PATTERN = /<(?:script\b[^>]*\bsrc|link\b[^>]*\bhref|img\b[^>]*\bsrc|source\b[^>]*\bsrcset)\s*=\s*["']((?:\/|\.\.\/|\.\/)[^"',\s]+)/giu;
4550
+ var CAPACITOR_CLIENT_FRAMEWORKS = new Set([
4551
+ "angular",
4552
+ "html",
4553
+ "htmx",
4554
+ "react",
4555
+ "svelte",
4556
+ "vue"
4557
+ ]);
4558
+ var CLIENT_ASSET_DIRECTORIES = ["assets", "html", "htmx", "indexes"];
2393
4559
  var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2394
4560
  var shellBootstrapModule = () => {
2395
- const candidate = ["js", "ts"].map((extension) => join7(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
4561
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
2396
4562
  if (candidate)
2397
4563
  return candidate;
2398
4564
  throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
2399
4565
  };
4566
+ var shellAuthModule = () => {
4567
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellAuth.${extension}`)).find(existsSync2);
4568
+ if (candidate)
4569
+ return candidate;
4570
+ throw new TypeError("AbsoluteJS mobile auth shell module is missing.");
4571
+ };
4572
+ var shellSyncModule = () => {
4573
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellSync.${extension}`)).find(existsSync2);
4574
+ if (candidate)
4575
+ return candidate;
4576
+ throw new TypeError("AbsoluteJS mobile Sync shell module is missing.");
4577
+ };
2400
4578
  var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
2401
4579
  var indexHtml = (appName) => `<!doctype html>
2402
4580
  <html>
@@ -2413,18 +4591,58 @@ var indexHtml = (appName) => `<!doctype html>
2413
4591
  </html>
2414
4592
  `;
2415
4593
  var sourceAssetPath = (buildDirectory, bundlePath) => {
2416
- const root = resolve8(buildDirectory);
2417
- const asset = resolve8(root, bundlePath.replace(/^\/+/, ""));
4594
+ const root = resolve9(buildDirectory);
4595
+ const asset = resolve9(root, bundlePath.replace(/^\/+/, ""));
2418
4596
  if (!asset.startsWith(`${root}/`)) {
2419
4597
  throw new TypeError("Mobile page bundle escaped the build directory.");
2420
4598
  }
2421
4599
  return asset;
2422
4600
  };
2423
- var buildShellBootstrap = async (staging) => {
4601
+ var importEntryTarget = (entry) => {
4602
+ if (typeof entry === "string")
4603
+ return entry;
4604
+ if (typeof entry === "object" && entry !== null)
4605
+ return Reflect.get(entry, "import");
4606
+ return;
4607
+ };
4608
+ var resolveProjectImport = async (projectRoot, specifier) => {
4609
+ const segments = specifier.split("/");
4610
+ const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
4611
+ const subpath = specifier.slice(packageName.length);
4612
+ const packageDirectory = join9(resolve9(projectRoot), "node_modules", packageName);
4613
+ const manifest = JSON.parse(await readFile11(join9(packageDirectory, "package.json"), "utf8"));
4614
+ const exports = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "exports") : undefined;
4615
+ const entry = typeof exports === "object" && exports !== null ? Reflect.get(exports, subpath ? `.${subpath}` : ".") : undefined;
4616
+ const target = importEntryTarget(entry);
4617
+ if (typeof target !== "string" || !target.startsWith("./"))
4618
+ throw new TypeError(`${specifier} does not publish an import entry.`);
4619
+ const resolved = resolve9(packageDirectory, target);
4620
+ if (!resolved.startsWith(`${resolve9(packageDirectory)}/`))
4621
+ throw new TypeError(`${specifier} has an unsafe import entry.`);
4622
+ return resolved;
4623
+ };
4624
+ var buildShellBootstrap = async (staging, auth, sync, storagePrefix, deviceCapabilities, projectRoot) => {
2424
4625
  const modulePath = shellBootstrapModule();
2425
- const entryPath = join7(staging, ".absolute-mobile-entry.ts");
2426
- await writeFile7(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
2427
- void startAbsoluteMobileShell();
4626
+ const authImport = auth ? `import { createAbsoluteMobileShellAuth } from ${JSON.stringify(shellAuthModule())};
4627
+ ` : "";
4628
+ const options = auth ? `{ createAuth: createAbsoluteMobileShellAuth${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : "";
4629
+ const syncImport = sync ? `import { installAbsoluteMobileShellSync } from ${JSON.stringify(shellSyncModule())};
4630
+ ` : "";
4631
+ const capabilityImports = (await Promise.all(deviceCapabilities.capabilities.map(async (name, index) => {
4632
+ const provider = deviceCapabilities.providers[name];
4633
+ if (!provider)
4634
+ throw new TypeError(`Missing device capability provider ${name}.`);
4635
+ return `import { ${provider.factory} as absoluteDeviceCapability${index} } from ${JSON.stringify(await resolveProjectImport(projectRoot, provider.module))};`;
4636
+ }))).join(`
4637
+ `);
4638
+ const capabilityOptions = deviceCapabilities.capabilities.map((name, index) => `${JSON.stringify(name)}: absoluteDeviceCapability${index}()`).join(", ");
4639
+ const entryPath = join9(staging, ".absolute-mobile-entry.ts");
4640
+ const baseAdapterModule = await resolveProjectImport(projectRoot, "@absolutejs/devices-capacitor");
4641
+ await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
4642
+ import { installCapacitorDeviceAdapterIfNative } from ${JSON.stringify(baseAdapterModule)};
4643
+ ${authImport}${syncImport}${capabilityImports}
4644
+ installCapacitorDeviceAdapterIfNative({ storagePrefix: ${JSON.stringify(storagePrefix)}${capabilityOptions ? `, ${capabilityOptions}` : ""} });
4645
+ void startAbsoluteMobileShell(${options});
2428
4646
  `);
2429
4647
  const build = await Bun.build({
2430
4648
  entrypoints: [entryPath],
@@ -2435,31 +4653,31 @@ void startAbsoluteMobileShell();
2435
4653
  if (!build.success || build.outputs.length !== 1) {
2436
4654
  throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
2437
4655
  }
2438
- await rename7(build.outputs[0]?.path ?? "", join7(staging, BOOTSTRAP_FILE));
2439
- await rm6(entryPath, { force: true });
4656
+ await rename9(build.outputs[0]?.path ?? "", join9(staging, BOOTSTRAP_FILE));
4657
+ await rm7(entryPath, { force: true });
2440
4658
  };
2441
4659
  var removePreviousBundle = async (backup, moved) => {
2442
4660
  if (!moved)
2443
4661
  return;
2444
- await rm6(backup, { force: true, recursive: true });
4662
+ await rm7(backup, { force: true, recursive: true });
2445
4663
  };
2446
4664
  var restorePreviousBundle = async (backup, destination, moved) => {
2447
4665
  if (!moved)
2448
4666
  return;
2449
- await rename7(backup, destination);
4667
+ await rename9(backup, destination);
2450
4668
  };
2451
4669
  var installBundle = async (staging, destination) => {
2452
4670
  const backup = `${destination}.previous-${crypto.randomUUID()}`;
2453
4671
  let movedPrevious = false;
2454
4672
  try {
2455
- await rename7(destination, backup);
4673
+ await rename9(destination, backup);
2456
4674
  movedPrevious = true;
2457
4675
  } catch (error) {
2458
4676
  if (!errorHasCode2(error, "ENOENT"))
2459
4677
  throw error;
2460
4678
  }
2461
4679
  try {
2462
- await rename7(staging, destination);
4680
+ await rename9(staging, destination);
2463
4681
  await removePreviousBundle(backup, movedPrevious);
2464
4682
  } catch (error) {
2465
4683
  await restorePreviousBundle(backup, destination, movedPrevious);
@@ -2467,21 +4685,62 @@ var installBundle = async (staging, destination) => {
2467
4685
  }
2468
4686
  };
2469
4687
  var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) => {
2470
- if (page.framework !== "react") {
2471
- throw new TypeError(`Capacitor spike currently supports React pages; ${page.pageId} is ${page.framework}.`);
4688
+ if (!CAPACITOR_CLIENT_FRAMEWORKS.has(page.framework)) {
4689
+ throw new TypeError(`Capacitor client rendering does not yet support ${page.framework} page ${page.pageId}.`);
2472
4690
  }
2473
- const extension = extname(page.bundlePath) || ".js";
4691
+ const extension = extname2(page.bundlePath) || ".js";
2474
4692
  const localBundlePath = `./pages/${page.bundleHash}${extension}`;
2475
4693
  const source = sourceAssetPath(buildDirectory, page.bundlePath);
2476
- await copyFile4(source, join7(staging, localBundlePath));
4694
+ await copyFile5(source, join9(staging, localBundlePath));
2477
4695
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
2478
- return { ...page, localBundlePath };
4696
+ let localStylePath;
4697
+ if (page.styleBundlePath && page.styleBundleHash) {
4698
+ const styleExtension = extname2(page.styleBundlePath) || ".css";
4699
+ localStylePath = `./styles/${page.styleBundleHash}${styleExtension}`;
4700
+ const styleSource = sourceAssetPath(buildDirectory, page.styleBundlePath);
4701
+ await mkdir9(dirname7(join9(staging, localStylePath)), {
4702
+ recursive: true
4703
+ });
4704
+ await copyFile5(styleSource, join9(staging, localStylePath));
4705
+ await copyAbsoluteClientDependencies(styleSource, buildDirectory, staging, copiedDependencies);
4706
+ }
4707
+ return {
4708
+ ...page,
4709
+ localBundlePath,
4710
+ ...localStylePath ? { localStylePath } : {}
4711
+ };
2479
4712
  };
2480
- var absoluteClientImports = async (sourcePath) => {
2481
- const source = await readFile8(sourcePath, "utf8");
2482
- return [...source.matchAll(CLIENT_IMPORT_PATTERN)].flatMap((match) => {
2483
- const [specifier] = match.slice(1);
2484
- return specifier ? [specifier.split(/[?#]/u, 1)[0] ?? specifier] : [];
4713
+ var absoluteClientImports = async (sourcePath, buildDirectory) => {
4714
+ const source = await readFile11(sourcePath, "utf8");
4715
+ const extension = extname2(sourcePath).toLowerCase();
4716
+ let scriptLoader;
4717
+ if (extension === ".tsx")
4718
+ scriptLoader = "tsx";
4719
+ else if (extension === ".ts")
4720
+ scriptLoader = "ts";
4721
+ else if (extension === ".jsx")
4722
+ scriptLoader = "jsx";
4723
+ else if ([".js", ".mjs", ".cjs"].includes(extension))
4724
+ scriptLoader = "js";
4725
+ const scriptImports = scriptLoader ? new Bun.Transpiler({ loader: scriptLoader }).scanImports(source).map(({ path }) => path) : [];
4726
+ const cssImports = extension === ".css" ? [...source.matchAll(CLIENT_CSS_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
4727
+ const markupImports = extension === ".html" ? [...source.matchAll(CLIENT_MARKUP_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
4728
+ return [...scriptImports, ...cssImports, ...markupImports].flatMap((specifier) => {
4729
+ if (!specifier)
4730
+ return [];
4731
+ if (!specifier.startsWith("/") && !specifier.startsWith("./") && !specifier.startsWith("../")) {
4732
+ return [];
4733
+ }
4734
+ const clean = specifier.split(/[?#]/u, 1)[0] ?? specifier;
4735
+ if (clean.startsWith("/"))
4736
+ return [clean];
4737
+ const resolved = resolve9(dirname7(sourcePath), clean);
4738
+ const root = resolve9(buildDirectory);
4739
+ const relativePath = relative8(root, resolved).replaceAll("\\", "/");
4740
+ if (relativePath === ".." || relativePath.startsWith("../")) {
4741
+ throw new TypeError(`Mobile client dependency escaped the build directory: ${specifier}`);
4742
+ }
4743
+ return [`/${relativePath}`];
2485
4744
  });
2486
4745
  };
2487
4746
  var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, copied) => {
@@ -2489,13 +4748,13 @@ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, co
2489
4748
  return;
2490
4749
  copied.add(specifier);
2491
4750
  const source = sourceAssetPath(buildDirectory, specifier);
2492
- const destination = join7(staging, specifier.replace(/^\/+/, ""));
2493
- await mkdir6(dirname4(destination), { recursive: true });
2494
- await copyFile4(source, destination);
4751
+ const destination = join9(staging, specifier.replace(/^\/+/, ""));
4752
+ await mkdir9(dirname7(destination), { recursive: true });
4753
+ await copyFile5(source, destination);
2495
4754
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
2496
4755
  };
2497
4756
  var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
2498
- const dependencies = await absoluteClientImports(sourcePath);
4757
+ const dependencies = await absoluteClientImports(sourcePath, buildDirectory);
2499
4758
  await Promise.all(dependencies.map((specifier) => copyAbsoluteClientDependency(specifier, buildDirectory, staging, copied)));
2500
4759
  };
2501
4760
  var materializeAbsoluteCapacitorWebBundle = async (options) => {
@@ -2503,69 +4762,89 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2503
4762
  throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
2504
4763
  }
2505
4764
  const destination = options.config.bundleDirectory;
2506
- await mkdir6(dirname4(destination), { recursive: true });
2507
- const staging = await mkdtemp4(join7(dirname4(destination), `.${basename(destination)}.stage-`));
4765
+ await mkdir9(dirname7(destination), { recursive: true });
4766
+ const staging = await mkdtemp4(join9(dirname7(destination), `.${basename3(destination)}.stage-`));
2508
4767
  try {
2509
- const pageDirectory = join7(staging, "pages");
2510
- await mkdir6(pageDirectory, { recursive: true });
4768
+ const pageDirectory = join9(staging, "pages");
4769
+ await mkdir9(pageDirectory, { recursive: true });
4770
+ await Promise.all(CLIENT_ASSET_DIRECTORIES.map((directory) => ({
4771
+ destination: join9(staging, directory),
4772
+ source: join9(options.buildDirectory, directory)
4773
+ })).filter(({ source }) => existsSync2(source)).map(({ destination: assetDestination, source }) => cp(source, assetDestination, { recursive: true })));
2511
4774
  const copiedDependencies = new Set;
2512
4775
  const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
2513
4776
  const manifest = {
2514
4777
  appBuild: options.artifact.appBuild,
4778
+ ...options.auth ? { auth: options.auth } : {},
2515
4779
  appId: options.config.appId,
2516
4780
  appName: options.config.appName,
2517
4781
  deepLinkHosts: options.config.deepLinkHosts,
2518
4782
  deepLinkScheme: options.config.deepLinkScheme,
4783
+ deviceCapabilities: options.deviceCapabilities.capabilities,
2519
4784
  entry: options.config.entry,
2520
4785
  format: ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
2521
4786
  pages,
2522
4787
  productionOrigin: options.config.productionOrigin,
2523
4788
  routes: options.artifact.routes,
2524
- runtime: options.artifact.runtime
4789
+ runtime: options.artifact.runtime,
4790
+ ...options.sync ? {
4791
+ sync: {
4792
+ background: {
4793
+ endpoint: new URL("/__absolute/sync/background", options.config.productionOrigin).href,
4794
+ intervalMinutes: 15
4795
+ },
4796
+ socketTickets: true,
4797
+ storageSchema: options.syncSchema ?? {
4798
+ components: [
4799
+ { id: "@absolutejs/app", version: 1 }
4800
+ ]
4801
+ }
4802
+ }
4803
+ } : {}
2525
4804
  };
2526
4805
  await Promise.all([
2527
- writeFile7(join7(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
4806
+ writeFile10(join9(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
2528
4807
  `),
2529
- writeFile7(join7(staging, INDEX_FILE), indexHtml(options.config.appName)),
2530
- buildShellBootstrap(staging)
4808
+ writeFile10(join9(staging, INDEX_FILE), indexHtml(options.config.appName)),
4809
+ buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true, `absolutejs.${options.auth?.clientId ?? options.config.appId}.`, options.deviceCapabilities, options.projectRoot)
2531
4810
  ]);
2532
4811
  await installBundle(staging, destination);
2533
4812
  return manifest;
2534
4813
  } catch (error) {
2535
- await rm6(staging, { force: true, recursive: true });
4814
+ await rm7(staging, { force: true, recursive: true });
2536
4815
  throw error;
2537
4816
  }
2538
4817
  };
2539
4818
 
2540
4819
  // src/mobile/materializedBundle.ts
2541
- import { createHash as createHash7 } from "crypto";
4820
+ import { createHash as createHash9 } from "crypto";
2542
4821
  import {
2543
- access as access6,
2544
- mkdir as mkdir7,
4822
+ access as access8,
4823
+ mkdir as mkdir10,
2545
4824
  mkdtemp as mkdtemp5,
2546
- readFile as readFile9,
2547
- rename as rename8,
2548
- rm as rm7,
2549
- writeFile as writeFile8
4825
+ readFile as readFile12,
4826
+ rename as rename10,
4827
+ rm as rm8,
4828
+ writeFile as writeFile11
2550
4829
  } from "fs/promises";
2551
- import { dirname as dirname5, join as join8, resolve as resolvePath2 } from "path";
4830
+ import { dirname as dirname8, join as join10, resolve as resolvePath3 } from "path";
2552
4831
  import { pathToFileURL } from "url";
2553
4832
  var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
2554
4833
  var CURRENT_BUNDLE_FILE = "current.json";
2555
4834
  var BUNDLES_DIRECTORY = "bundles";
2556
4835
  var ARTIFACT_FILE2 = "artifact.json";
2557
4836
  var BUNDLE_ID_PATTERN = /^amb_[a-f0-9]{64}$/;
2558
- var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4837
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2559
4838
  var errorHasCode3 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2560
4839
  var bundleIdFor = (currentReleaseId, releases) => {
2561
4840
  const identity = JSON.stringify({
2562
4841
  currentReleaseId,
2563
4842
  releases: releases.map(({ releaseId }) => releaseId)
2564
4843
  });
2565
- return `amb_${createHash7("sha256").update(identity).digest("hex")}`;
4844
+ return `amb_${createHash9("sha256").update(identity).digest("hex")}`;
2566
4845
  };
2567
4846
  var parseBundleIndex = (value) => {
2568
- if (!isRecord6(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)) {
4847
+ 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)) {
2569
4848
  throw new TypeError("Invalid materialized mobile compatibility bundle.");
2570
4849
  }
2571
4850
  const releases = value.releases.map(parseAbsoluteMobileCompatibilityArtifact);
@@ -2588,30 +4867,30 @@ var parseBundleIndex = (value) => {
2588
4867
  };
2589
4868
  };
2590
4869
  var writeRelease = async (root, release) => {
2591
- const directory = join8(root, release.artifact.releaseId);
2592
- const producerPath = join8(directory, release.artifact.producer.module);
2593
- await mkdir7(dirname5(producerPath), { recursive: true });
4870
+ const directory = join10(root, release.artifact.releaseId);
4871
+ const producerPath = join10(directory, release.artifact.producer.module);
4872
+ await mkdir10(dirname8(producerPath), { recursive: true });
2594
4873
  await Promise.all([
2595
- writeFile8(join8(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
4874
+ writeFile11(join10(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
2596
4875
  `),
2597
- writeFile8(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
4876
+ writeFile11(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
2598
4877
  ]);
2599
4878
  };
2600
4879
  var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
2601
- const destination = join8(bundlesRoot, bundleId);
4880
+ const destination = join10(bundlesRoot, bundleId);
2602
4881
  try {
2603
- await access6(destination);
4882
+ await access8(destination);
2604
4883
  return destination;
2605
4884
  } catch (error) {
2606
4885
  if (!errorHasCode3(error, "ENOENT"))
2607
4886
  throw error;
2608
4887
  }
2609
- const staging = await mkdtemp5(join8(bundlesRoot, ".stage-"));
4888
+ const staging = await mkdtemp5(join10(bundlesRoot, ".stage-"));
2610
4889
  try {
2611
4890
  await Promise.all(releases.map((release) => writeRelease(staging, release)));
2612
- await rename8(staging, destination);
4891
+ await rename10(staging, destination);
2613
4892
  } catch (error) {
2614
- await rm7(staging, { force: true, recursive: true });
4893
+ await rm8(staging, { force: true, recursive: true });
2615
4894
  if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
2616
4895
  return destination;
2617
4896
  }
@@ -2622,7 +4901,7 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
2622
4901
  var readCompatibilityModule = (modulePath) => import(pathToFileURL(modulePath).href);
2623
4902
  var resolveProducerHandler = (loaded, exportName) => {
2624
4903
  const value = loaded[exportName];
2625
- if (!isRecord6(value) || typeof value.handle !== "function") {
4904
+ if (!isRecord7(value) || typeof value.handle !== "function") {
2626
4905
  throw new TypeError(`Compatibility producer export ${exportName} must expose handle(request).`);
2627
4906
  }
2628
4907
  const { handle } = value;
@@ -2639,16 +4918,16 @@ var resolveProducerHandler = (loaded, exportName) => {
2639
4918
  };
2640
4919
  };
2641
4920
  var loadAbsoluteMobileMaterializedBundle = async (root) => {
2642
- const resolvedRoot = resolvePath2(root);
2643
- const serialized = await readFile9(join8(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
4921
+ const resolvedRoot = resolvePath3(root);
4922
+ const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2644
4923
  const parsed = JSON.parse(serialized);
2645
4924
  const index = parseBundleIndex(parsed);
2646
- const bundleRoot = join8(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
4925
+ const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2647
4926
  return {
2648
4927
  artifacts: index.releases,
2649
4928
  currentReleaseId: index.currentReleaseId,
2650
4929
  loadProducer: async (artifact) => {
2651
- const modulePath = join8(bundleRoot, artifact.releaseId, artifact.producer.module);
4930
+ const modulePath = join10(bundleRoot, artifact.releaseId, artifact.producer.module);
2652
4931
  await verifyAbsoluteMobileCompatibilityProducer({
2653
4932
  artifact,
2654
4933
  producer: Bun.file(modulePath)
@@ -2674,9 +4953,9 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
2674
4953
  }
2675
4954
  return release;
2676
4955
  });
2677
- const root = resolvePath2(input.root);
2678
- const bundlesRoot = join8(root, BUNDLES_DIRECTORY);
2679
- await mkdir7(bundlesRoot, { recursive: true });
4956
+ const root = resolvePath3(input.root);
4957
+ const bundlesRoot = join10(root, BUNDLES_DIRECTORY);
4958
+ await mkdir10(bundlesRoot, { recursive: true });
2680
4959
  const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
2681
4960
  await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
2682
4961
  const index = {
@@ -2685,22 +4964,22 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
2685
4964
  format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
2686
4965
  releases: artifacts
2687
4966
  };
2688
- const pointerPath = join8(root, CURRENT_BUNDLE_FILE);
2689
- const temporaryPointerPath = join8(root, `.current-${crypto.randomUUID()}.json`);
2690
- await writeFile8(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
4967
+ const pointerPath = join10(root, CURRENT_BUNDLE_FILE);
4968
+ const temporaryPointerPath = join10(root, `.current-${crypto.randomUUID()}.json`);
4969
+ await writeFile11(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
2691
4970
  `, { flag: "wx" });
2692
- await rename8(temporaryPointerPath, pointerPath);
4971
+ await rename10(temporaryPointerPath, pointerPath);
2693
4972
  return index;
2694
4973
  };
2695
4974
  var readAbsoluteMobileMaterializedReleases = async (root) => {
2696
- const resolvedRoot = resolvePath2(root);
4975
+ const resolvedRoot = resolvePath3(root);
2697
4976
  try {
2698
- const serialized = await readFile9(join8(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
4977
+ const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2699
4978
  const parsed = JSON.parse(serialized);
2700
4979
  const index = parseBundleIndex(parsed);
2701
- const bundleRoot = join8(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
4980
+ const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2702
4981
  return Promise.all(index.releases.map(async (artifact) => {
2703
- const producer = Bun.file(join8(bundleRoot, artifact.releaseId, artifact.producer.module));
4982
+ const producer = Bun.file(join10(bundleRoot, artifact.releaseId, artifact.producer.module));
2704
4983
  await verifyAbsoluteMobileCompatibilityProducer({
2705
4984
  artifact,
2706
4985
  producer
@@ -2714,6 +4993,271 @@ var readAbsoluteMobileMaterializedReleases = async (root) => {
2714
4993
  }
2715
4994
  };
2716
4995
 
4996
+ // src/mobile/nativeAuth.ts
4997
+ import { readFileSync as readFileSync2 } from "fs";
4998
+ import { join as join11 } from "path";
4999
+ var ABSOLUTE_AUTH_PACKAGE = "@absolutejs/auth";
5000
+ var ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV = "ABSOLUTE_AUTH_NATIVE_CLIENTS";
5001
+ var ABSOLUTE_NATIVE_AUTH_SCOPES = ["openid", "profile"];
5002
+ var ABSOLUTE_SYNC_PACKAGE = "@absolutejs/sync";
5003
+ var readPackageManifest = (projectRoot) => {
5004
+ try {
5005
+ return JSON.parse(readFileSync2(join11(projectRoot, "package.json"), "utf8"));
5006
+ } catch {
5007
+ return;
5008
+ }
5009
+ };
5010
+ var packageManifestHas = (manifest, packageName) => {
5011
+ if (typeof manifest !== "object" || manifest === null)
5012
+ return false;
5013
+ return [
5014
+ Reflect.get(manifest, "dependencies"),
5015
+ Reflect.get(manifest, "devDependencies"),
5016
+ Reflect.get(manifest, "optionalDependencies"),
5017
+ Reflect.get(manifest, "peerDependencies")
5018
+ ].some((dependencies) => typeof dependencies === "object" && dependencies !== null && Object.hasOwn(dependencies, packageName));
5019
+ };
5020
+ var createAbsoluteMobileAuthManifest = (config) => {
5021
+ const scheme = config.deepLinkScheme ?? config.appId.toLowerCase();
5022
+ return {
5023
+ clientId: `absolutejs-native:${config.appId}`,
5024
+ issuer: config.productionOrigin,
5025
+ redirectUri: `${scheme}://auth/callback`,
5026
+ scopes: [...ABSOLUTE_NATIVE_AUTH_SCOPES]
5027
+ };
5028
+ };
5029
+ var installAbsoluteMobileAuthEnvironment = (projectRoot, config) => {
5030
+ const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
5031
+ const serialized = serializeAbsoluteMobileAuthEnvironment(config, auth);
5032
+ if (serialized === undefined)
5033
+ delete process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV];
5034
+ else
5035
+ process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV] = serialized;
5036
+ return auth;
5037
+ };
5038
+ var projectUsesAbsoluteAuth = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_AUTH_PACKAGE);
5039
+ var projectUsesAbsoluteSync = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_SYNC_PACKAGE);
5040
+ var resolveAbsoluteMobileAuthManifest = (projectRoot, config) => projectUsesAbsoluteAuth(projectRoot) ? createAbsoluteMobileAuthManifest(config) : undefined;
5041
+ var serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefined ? undefined : JSON.stringify([
5042
+ {
5043
+ ...auth,
5044
+ name: `${config.appName} native app`
5045
+ }
5046
+ ]);
5047
+
5048
+ // src/mobile/buildPipeline.ts
5049
+ init_syncSchema();
5050
+
5051
+ // src/mobile/deviceCapabilities.ts
5052
+ import { readFileSync as readFileSync4 } from "fs";
5053
+ import { extname as extname3, join as join13, relative as relative9, resolve as resolve11 } from "path";
5054
+ import ts from "typescript";
5055
+ var DEVICES_PACKAGE = "@absolutejs/devices";
5056
+ var CAPACITOR_ADAPTER = "@absolutejs/devices-capacitor";
5057
+ var SOURCE_GLOB = new Bun.Glob("**/*.{js,jsx,ts,tsx,svelte,vue}");
5058
+ var IGNORED_DIRECTORIES = new Set([
5059
+ ".absolutejs",
5060
+ ".git",
5061
+ ".test-builds",
5062
+ ".test-shards",
5063
+ "build",
5064
+ "dist",
5065
+ "node_modules",
5066
+ "test",
5067
+ "tests"
5068
+ ]);
5069
+ var IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
5070
+ var CAPACITOR_MODULE_PATTERN = /^@absolutejs\/devices-capacitor\/[a-z][a-z0-9-]*$/u;
5071
+ var CAPACITOR_PACKAGE_PATTERN = /^@capacitor\/[a-z][a-z0-9-]*@\d+\.\d+\.\d+$/u;
5072
+ var ANDROID_PERMISSION_PATTERN = /^android\.permission\.[A-Z][A-Z0-9_]*$/u;
5073
+ var IOS_USAGE_DESCRIPTIONS = new Set([
5074
+ "camera",
5075
+ "location-always",
5076
+ "location-when-in-use",
5077
+ "photo-library",
5078
+ "photo-library-add"
5079
+ ]);
5080
+ var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5081
+ var readJson = (path) => {
5082
+ const value = JSON.parse(readFileSync4(path, "utf8"));
5083
+ if (!object2(value))
5084
+ throw new TypeError(`${path} must contain an object.`);
5085
+ return value;
5086
+ };
5087
+ var text = (value, field) => {
5088
+ if (typeof value !== "string" || value.length === 0)
5089
+ throw new TypeError(`${field} must be a non-empty string.`);
5090
+ return value;
5091
+ };
5092
+ var androidPermissions = (value, field) => {
5093
+ if (value === undefined)
5094
+ return;
5095
+ if (!object2(value))
5096
+ throw new TypeError(`${field} must be an object.`);
5097
+ const { permissions } = value;
5098
+ if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
5099
+ throw new TypeError(`${field}.permissions must contain Android permission names.`);
5100
+ return [...permissions];
5101
+ };
5102
+ var iosUsageDescriptions = (value, field) => {
5103
+ if (value === undefined)
5104
+ return;
5105
+ if (!object2(value))
5106
+ throw new TypeError(`${field} must be an object.`);
5107
+ const { usageDescriptions } = value;
5108
+ if (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose)))
5109
+ throw new TypeError(`${field}.usageDescriptions contains an unsupported purpose.`);
5110
+ return [...usageDescriptions];
5111
+ };
5112
+ var parseProvider = (name, value) => {
5113
+ if (!IDENTIFIER_PATTERN.test(name))
5114
+ throw new TypeError("Device capability names must be identifiers.");
5115
+ if (!object2(value))
5116
+ throw new TypeError(`Device capability ${name} must be an object.`);
5117
+ const factory = text(value.factory, `${name}.factory`);
5118
+ const module = text(value.module, `${name}.module`);
5119
+ if (!IDENTIFIER_PATTERN.test(factory))
5120
+ throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
5121
+ if (!CAPACITOR_MODULE_PATTERN.test(module))
5122
+ throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
5123
+ if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
5124
+ throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
5125
+ let native;
5126
+ const { native: nativeMetadata } = value;
5127
+ if (nativeMetadata !== undefined) {
5128
+ if (!object2(nativeMetadata))
5129
+ throw new TypeError(`${name}.native must be an object.`);
5130
+ const { android, ios } = nativeMetadata;
5131
+ const permissions = androidPermissions(android, `${name}.native.android`);
5132
+ const usageDescriptions = iosUsageDescriptions(ios, `${name}.native.ios`);
5133
+ native = {
5134
+ ...permissions === undefined ? {} : { android: { permissions } },
5135
+ ...usageDescriptions === undefined ? {} : { ios: { usageDescriptions } }
5136
+ };
5137
+ }
5138
+ return {
5139
+ factory,
5140
+ module,
5141
+ ...native === undefined ? {} : { native },
5142
+ packages: [...value.packages]
5143
+ };
5144
+ };
5145
+ var absoluteDeviceNativeRequirements = (plan) => ({
5146
+ androidPermissions: [
5147
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
5148
+ ].sort(),
5149
+ iosUsageDescriptions: [
5150
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
5151
+ ].sort()
5152
+ });
5153
+ var loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
5154
+ const path = join13(resolve11(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
5155
+ const manifest = readJson(path);
5156
+ const { absolutejs } = manifest;
5157
+ const devices = object2(absolutejs) ? absolutejs.devices : undefined;
5158
+ if (!object2(devices) || devices.format !== 1 || devices.provider !== "capacitor" || !object2(devices.capabilities))
5159
+ throw new TypeError(`${CAPACITOR_ADAPTER} does not publish supported capability metadata.`);
5160
+ const entries = Object.entries(devices.capabilities).map(([name, provider]) => ({
5161
+ name,
5162
+ provider: parseProvider(name, provider)
5163
+ }));
5164
+ return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider }) => [name, provider]));
5165
+ };
5166
+ var isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment));
5167
+ var importedCapabilities = (source, file) => {
5168
+ const names = new Set;
5169
+ const namespaces = new Set;
5170
+ const visit = (node) => {
5171
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
5172
+ const bindings = node.importClause?.namedBindings;
5173
+ if (bindings && ts.isNamedImports(bindings)) {
5174
+ for (const element of bindings.elements)
5175
+ if (!element.isTypeOnly)
5176
+ names.add((element.propertyName ?? element.name).text);
5177
+ }
5178
+ if (bindings && ts.isNamespaceImport(bindings))
5179
+ namespaces.add(bindings.name.text);
5180
+ }
5181
+ if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
5182
+ for (const element of node.exportClause.elements)
5183
+ if (!element.isTypeOnly)
5184
+ names.add((element.propertyName ?? element.name).text);
5185
+ }
5186
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
5187
+ names.add(node.name.text);
5188
+ ts.forEachChild(node, visit);
5189
+ };
5190
+ const extension = extname3(file).toLowerCase();
5191
+ const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
5192
+ for (const [index, script] of sources.entries())
5193
+ visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
5194
+ return names;
5195
+ };
5196
+ var assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
5197
+ const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
5198
+ const mismatched = plan.requiredPackages.filter((spec) => {
5199
+ const separator = spec.lastIndexOf("@");
5200
+ const packageName = spec.slice(0, separator);
5201
+ if (missing.includes(spec))
5202
+ return false;
5203
+ try {
5204
+ return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
5205
+ } catch {
5206
+ return true;
5207
+ }
5208
+ });
5209
+ const unmet = [...missing, ...mismatched];
5210
+ if (unmet.length > 0)
5211
+ throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
5212
+ };
5213
+ var directAbsoluteProjectPackages = (projectRoot) => {
5214
+ const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
5215
+ const packages = new Set;
5216
+ for (const field of ["dependencies", "devDependencies"]) {
5217
+ const dependencies = manifest[field];
5218
+ if (object2(dependencies))
5219
+ for (const name of Object.keys(dependencies))
5220
+ packages.add(name);
5221
+ }
5222
+ return packages;
5223
+ };
5224
+ var discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
5225
+ const root = resolve11(projectRoot);
5226
+ const known = new Set(Object.keys(providers));
5227
+ const capabilities = new Set;
5228
+ for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
5229
+ const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
5230
+ if (isIgnored(portable))
5231
+ continue;
5232
+ const source = readFileSync4(resolve11(root, portable), "utf8");
5233
+ for (const name of importedCapabilities(source, portable))
5234
+ if (known.has(name))
5235
+ capabilities.add(name);
5236
+ }
5237
+ return [...capabilities].sort();
5238
+ };
5239
+ var missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
5240
+ const packageName = spec.slice(0, spec.lastIndexOf("@"));
5241
+ return !directPackages.has(packageName);
5242
+ });
5243
+ var resolveAbsoluteDeviceCapabilityPlan = (projectRoot) => {
5244
+ const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot);
5245
+ const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
5246
+ const providers = {};
5247
+ for (const name of capabilities) {
5248
+ const provider = allProviders[name];
5249
+ if (provider)
5250
+ providers[name] = provider;
5251
+ }
5252
+ return {
5253
+ capabilities,
5254
+ providers,
5255
+ requiredPackages: [
5256
+ ...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
5257
+ ].sort()
5258
+ };
5259
+ };
5260
+
2717
5261
  // src/mobile/buildPipeline.ts
2718
5262
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
2719
5263
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
@@ -2724,12 +5268,11 @@ var serverExportName = (loaded, app) => {
2724
5268
  return "app";
2725
5269
  return "default";
2726
5270
  };
2727
- var restoreBuildDirectory = (previous) => {
2728
- if (previous !== undefined) {
2729
- process.env.ABSOLUTE_BUILD_DIR = previous;
2730
- return;
2731
- }
2732
- delete process.env.ABSOLUTE_BUILD_DIR;
5271
+ var restoreEnvironmentVariable = (name, previous) => {
5272
+ if (previous !== undefined)
5273
+ process.env[name] = previous;
5274
+ else
5275
+ delete process.env[name];
2733
5276
  };
2734
5277
  var requireRelease = (releases, releaseId) => {
2735
5278
  const release = releases.get(releaseId);
@@ -2749,11 +5292,11 @@ var loadServerApp = async (producerPath) => {
2749
5292
  return { app, exportName };
2750
5293
  };
2751
5294
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2752
- const buildDirectory = resolve9(options.buildDirectory);
5295
+ const buildDirectory = resolve12(options.buildDirectory);
2753
5296
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
2754
- const root = join9(buildDirectory, ".absolutejs", "mobile-compatibility");
5297
+ const root = join14(buildDirectory, ".absolutejs", "mobile-compatibility");
2755
5298
  const [manifestSource, previous] = await Promise.all([
2756
- readFile10(join9(buildDirectory, "manifest.json"), "utf8"),
5299
+ readFile13(join14(buildDirectory, "manifest.json"), "utf8"),
2757
5300
  readAbsoluteMobileMaterializedReleases(root)
2758
5301
  ]);
2759
5302
  const manifest = JSON.parse(manifestSource);
@@ -2761,12 +5304,20 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2761
5304
  throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
2762
5305
  }
2763
5306
  const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
5307
+ const previousCompiledRuntime = process.env.ABSOLUTE_COMPILED_RUNTIME;
5308
+ const previousConfigPath = process.env.ABSOLUTE_CONFIG;
2764
5309
  process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
5310
+ process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
5311
+ if (options.configPath) {
5312
+ process.env.ABSOLUTE_CONFIG = resolve12(options.projectRoot, options.configPath);
5313
+ }
2765
5314
  let loaded;
2766
5315
  try {
2767
- loaded = await loadServerApp(resolve9(options.producerPath));
5316
+ loaded = await loadServerApp(resolve12(options.producerPath));
2768
5317
  } finally {
2769
- restoreBuildDirectory(previousBuildDirectory);
5318
+ restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
5319
+ restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
5320
+ restoreEnvironmentVariable("ABSOLUTE_CONFIG", previousConfigPath);
2770
5321
  }
2771
5322
  const current = await buildAbsoluteMobileCompatibilityRelease({
2772
5323
  app: loaded.app,
@@ -2775,9 +5326,17 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2775
5326
  manifest,
2776
5327
  previousArtifacts: previous.map(({ artifact }) => artifact),
2777
5328
  producerExport: loaded.exportName,
2778
- producerPath: resolve9(options.producerPath),
5329
+ producerPath: resolve12(options.producerPath),
2779
5330
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
2780
5331
  });
5332
+ const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
5333
+ const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
5334
+ const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
5335
+ const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot);
5336
+ assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
5337
+ if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
5338
+ 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.");
5339
+ }
2781
5340
  const releasesById = new Map([current, ...previous].map((release) => [
2782
5341
  release.artifact.releaseId,
2783
5342
  release
@@ -2790,11 +5349,127 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2790
5349
  });
2791
5350
  await materializeAbsoluteCapacitorWebBundle({
2792
5351
  artifact: current.artifact,
5352
+ ...auth ? { auth } : {},
2793
5353
  buildDirectory,
2794
- config: mobile
5354
+ config: mobile,
5355
+ deviceCapabilities,
5356
+ projectRoot: options.projectRoot,
5357
+ ...sync ? { sync: true } : {},
5358
+ ...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
2795
5359
  });
2796
5360
  return current.artifact;
2797
5361
  };
5362
+
5363
+ // src/mobile/index.ts
5364
+ init_syncSchema();
5365
+
5366
+ // node_modules/@absolutejs/sync/dist/client/runtimeTransport.js
5367
+ var RUNTIME_TRANSPORT2 = Symbol.for("@absolutejs/sync/client-runtime-transport");
5368
+ var host2 = globalThis;
5369
+ var isRegistry2 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients"));
5370
+ var registry2 = (() => {
5371
+ const existing = host2[RUNTIME_TRANSPORT2];
5372
+ if (isRegistry2(existing))
5373
+ return existing;
5374
+ if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
5375
+ Reflect.set(existing, "clients", []);
5376
+ return existing;
5377
+ }
5378
+ const created = { clients: [], installations: [] };
5379
+ Object.defineProperty(host2, RUNTIME_TRANSPORT2, {
5380
+ configurable: false,
5381
+ enumerable: false,
5382
+ value: created,
5383
+ writable: false
5384
+ });
5385
+ return created;
5386
+ })();
5387
+ var maximum = (values) => {
5388
+ const present = values.filter((value) => value !== undefined);
5389
+ return present.length === 0 ? undefined : Math.max(...present);
5390
+ };
5391
+ var minimum = (values) => {
5392
+ const present = values.filter((value) => value !== undefined);
5393
+ return present.length === 0 ? undefined : Math.min(...present);
5394
+ };
5395
+ var inspectSyncRuntime = async () => {
5396
+ const clients = [...registry2.clients];
5397
+ const statuses = clients.map((client) => client.status());
5398
+ const deadLetters = (await Promise.all(clients.map((client) => client.listDeadLetters()))).flat().map((record) => ({
5399
+ attempts: record.attempts,
5400
+ ...record.rejection?.code ? { code: record.rejection.code } : {},
5401
+ createdAt: record.createdAt,
5402
+ ...record.deadLetteredAt === undefined ? {} : { deadLetteredAt: record.deadLetteredAt },
5403
+ ...record.rejection?.kind ? { kind: record.rejection.kind } : {},
5404
+ ...record.rejection?.message ? { message: record.rejection.message } : {},
5405
+ name: record.name,
5406
+ operationId: record.operationId
5407
+ })).sort((left, right) => (left.deadLetteredAt ?? left.createdAt) - (right.deadLetteredAt ?? right.createdAt) || left.operationId.localeCompare(right.operationId));
5408
+ const lastError = statuses.findLast((status) => status.lastError)?.lastError;
5409
+ return {
5410
+ automaticResolutions: statuses.reduce((total, status) => total + status.automaticResolutions, 0),
5411
+ clients: clients.length,
5412
+ conflicts: deadLetters.filter((record) => record.kind === "conflict").length,
5413
+ deadLetters,
5414
+ ...lastError ? { lastError } : {},
5415
+ ...maximum(statuses.map((status) => status.lastSuccessfulPullAt)) === undefined ? {} : {
5416
+ lastSuccessfulPullAt: maximum(statuses.map((status) => status.lastSuccessfulPullAt))
5417
+ },
5418
+ ...maximum(statuses.map((status) => status.lastSuccessfulPushAt)) === undefined ? {} : {
5419
+ lastSuccessfulPushAt: maximum(statuses.map((status) => status.lastSuccessfulPushAt))
5420
+ },
5421
+ ...minimum(statuses.map((status) => status.oldestDeadLetterAt)) === undefined ? {} : {
5422
+ oldestDeadLetterAt: minimum(statuses.map((status) => status.oldestDeadLetterAt))
5423
+ },
5424
+ ...minimum(statuses.map((status) => status.oldestPendingAt)) === undefined ? {} : {
5425
+ oldestPendingAt: minimum(statuses.map((status) => status.oldestPendingAt))
5426
+ },
5427
+ pending: statuses.reduce((total, status) => total + status.pending, 0)
5428
+ };
5429
+ };
5430
+ var clientWithDeadLetter = async (operationId) => {
5431
+ for (const client of registry2.clients)
5432
+ if ((await client.listDeadLetters()).some((record) => record.operationId === operationId))
5433
+ return client;
5434
+ throw new Error(`Unknown Sync dead letter "${operationId}"`);
5435
+ };
5436
+ var retrySyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).retryDeadLetter(operationId);
5437
+ var discardSyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).discardDeadLetter(operationId);
5438
+ var rebaseSyncRuntimeDeadLetter = async (operationId, args) => (await clientWithDeadLetter(operationId)).rebaseDeadLetter(operationId, args);
5439
+
5440
+ // src/mobile/syncRemediation.ts
5441
+ var REMEDIATION_REGISTRY = Symbol.for("@absolutejs/mobile-sync-remediation");
5442
+ var LAST_INSTALLATION_OFFSET = -1;
5443
+ var isRegistry3 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
5444
+ var resolveRegistry = () => {
5445
+ const existing = Reflect.get(globalThis, REMEDIATION_REGISTRY);
5446
+ if (isRegistry3(existing))
5447
+ return existing;
5448
+ const created = { installations: [] };
5449
+ Object.defineProperty(globalThis, REMEDIATION_REGISTRY, {
5450
+ configurable: false,
5451
+ enumerable: false,
5452
+ value: created,
5453
+ writable: false
5454
+ });
5455
+ return created;
5456
+ };
5457
+ var registry3 = resolveRegistry();
5458
+ var getAbsoluteMobileSyncRemediation = () => registry3.installations.at(LAST_INSTALLATION_OFFSET)?.bridge;
5459
+ var installAbsoluteMobileSyncRemediation = (bridge = {
5460
+ discard: discardSyncRuntimeDeadLetter,
5461
+ inspect: inspectSyncRuntime,
5462
+ rebase: rebaseSyncRuntimeDeadLetter,
5463
+ retry: retrySyncRuntimeDeadLetter
5464
+ }) => {
5465
+ const installation = { bridge };
5466
+ registry3.installations.push(installation);
5467
+ return () => {
5468
+ const index = registry3.installations.indexOf(installation);
5469
+ if (index >= 0)
5470
+ registry3.installations.splice(index, 1);
5471
+ };
5472
+ };
2798
5473
  // src/mobile/compatibilityDispatcher.ts
2799
5474
  import { Elysia as Elysia2 } from "elysia";
2800
5475
 
@@ -2816,6 +5491,64 @@ var ensureProducerStorage = () => {
2816
5491
  var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
2817
5492
 
2818
5493
  // src/mobile/compatibilityDispatcher.ts
5494
+ var MOBILE_WEBVIEW_ORIGINS = new Set([
5495
+ "capacitor://localhost",
5496
+ "http://localhost",
5497
+ "https://localhost"
5498
+ ]);
5499
+ var MOBILE_REQUEST_HEADER_NAMES = Object.values(MOBILE_PAGE_REQUEST_HEADERS);
5500
+ var MOBILE_CORS_ALLOW_HEADERS = [
5501
+ "accept",
5502
+ "content-type",
5503
+ "authorization",
5504
+ "hx-current-url",
5505
+ "hx-request",
5506
+ "hx-target",
5507
+ "hx-trigger",
5508
+ "hx-trigger-name",
5509
+ ...MOBILE_REQUEST_HEADER_NAMES
5510
+ ].join(", ");
5511
+ var MOBILE_CORS_METHODS = new Set([
5512
+ "DELETE",
5513
+ "GET",
5514
+ "HEAD",
5515
+ "OPTIONS",
5516
+ "PATCH",
5517
+ "POST",
5518
+ "PUT"
5519
+ ]);
5520
+ var mobileWebViewOrigin = (request) => {
5521
+ const origin = request.headers.get("origin");
5522
+ return origin && MOBILE_WEBVIEW_ORIGINS.has(origin) ? origin : undefined;
5523
+ };
5524
+ var applyMobileCorsHeaders = (response, origin) => {
5525
+ response.headers.set("access-control-allow-credentials", "true");
5526
+ response.headers.set("access-control-allow-origin", origin);
5527
+ response.headers.append("vary", "Origin");
5528
+ return response;
5529
+ };
5530
+ var mobilePreflightResponse = (request) => {
5531
+ if (request.method !== "OPTIONS")
5532
+ return;
5533
+ const origin = mobileWebViewOrigin(request);
5534
+ if (!origin)
5535
+ return;
5536
+ const requestedHeaders = request.headers.get("access-control-request-headers");
5537
+ const requestedMethod = request.headers.get("access-control-request-method")?.toUpperCase() ?? "";
5538
+ if (!MOBILE_CORS_METHODS.has(requestedMethod))
5539
+ return;
5540
+ return new Response(null, {
5541
+ headers: {
5542
+ "access-control-allow-credentials": "true",
5543
+ "access-control-allow-headers": requestedHeaders || MOBILE_CORS_ALLOW_HEADERS,
5544
+ "access-control-allow-methods": [...MOBILE_CORS_METHODS].join(", "),
5545
+ "access-control-allow-origin": origin,
5546
+ "access-control-max-age": "600",
5547
+ vary: "Origin, Access-Control-Request-Headers"
5548
+ },
5549
+ status: 204
5550
+ });
5551
+ };
2819
5552
  var artifactOwnsRequest = (artifact, pageId, request) => {
2820
5553
  const { pathname } = new URL(request.url);
2821
5554
  return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
@@ -2843,6 +5576,9 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2843
5576
  return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
2844
5577
  if (getCurrentAbsoluteMobileProducerContext())
2845
5578
  return;
5579
+ const preflight = mobilePreflightResponse(request);
5580
+ if (preflight)
5581
+ return preflight;
2846
5582
  const parsed = parseAbsoluteMobilePageRequest(request);
2847
5583
  if (parsed.kind !== "mobile")
2848
5584
  return;
@@ -2866,23 +5602,28 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2866
5602
  console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
2867
5603
  return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
2868
5604
  }
5605
+ }).afterHandle("global", ({ request, responseValue }) => {
5606
+ const origin = mobileWebViewOrigin(request);
5607
+ if (!origin || !(responseValue instanceof Response))
5608
+ return;
5609
+ applyMobileCorsHeaders(responseValue, origin);
2869
5610
  }).as("global");
2870
5611
  };
2871
5612
  // src/mobile/nativeDeepLinks.ts
2872
- import { readFile as readFile11, rename as rename9, writeFile as writeFile9 } from "fs/promises";
2873
- import { join as join10 } from "path";
5613
+ import { readFile as readFile14, rename as rename11, writeFile as writeFile12 } from "fs/promises";
5614
+ import { join as join15 } from "path";
2874
5615
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
2875
5616
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
2876
5617
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
2877
5618
  var NOT_FOUND = -1;
2878
5619
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2879
5620
  var writeChangedFile = async (path, source) => {
2880
- const current = await readFile11(path, "utf8");
5621
+ const current = await readFile14(path, "utf8");
2881
5622
  if (current === source)
2882
5623
  return false;
2883
5624
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2884
- await writeFile9(temporary, source, { flag: "wx" });
2885
- await rename9(temporary, path);
5625
+ await writeFile12(temporary, source, { flag: "wx" });
5626
+ await rename11(temporary, path);
2886
5627
  return true;
2887
5628
  };
2888
5629
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -2906,7 +5647,7 @@ var replaceManagedRegion = (source, region, insertAt) => {
2906
5647
  return `${source.slice(0, index)}${region}${source.slice(index)}`;
2907
5648
  };
2908
5649
  var androidRegion = (config) => {
2909
- const hosts = config.deepLinkHosts.map((host) => ` <data android:scheme="https" android:host="${escapeXml(host)}" />`).join(`
5650
+ const hosts = config.deepLinkHosts.map((host3) => ` <data android:scheme="https" android:host="${escapeXml(host3)}" />`).join(`
2910
5651
  `);
2911
5652
  const customScheme = config.deepLinkScheme ? `
2912
5653
 
@@ -2927,8 +5668,8 @@ ${hosts}
2927
5668
  `;
2928
5669
  };
2929
5670
  var configureAndroid = async (config) => {
2930
- const path = join10(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
2931
- const source = await readFile11(path, "utf8");
5671
+ const path = join15(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
5672
+ const source = await readFile14(path, "utf8");
2932
5673
  const mainActivity = source.indexOf('android:name=".MainActivity"');
2933
5674
  if (mainActivity === NOT_FOUND) {
2934
5675
  throw new TypeError("Android MainActivity was not found.");
@@ -2953,8 +5694,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
2953
5694
  ${END_MARKER}
2954
5695
  `;
2955
5696
  var configureIosInfo = async (config) => {
2956
- const path = join10(config.nativeProjectDirectory, "ios/App/App/Info.plist");
2957
- const source = await readFile11(path, "utf8");
5697
+ const path = join15(config.nativeProjectDirectory, "ios/App/App/Info.plist");
5698
+ const source = await readFile14(path, "utf8");
2958
5699
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
2959
5700
  ${END_MARKER}
2960
5701
  `;
@@ -2962,7 +5703,7 @@ var configureIosInfo = async (config) => {
2962
5703
  return writeChangedFile(path, updated);
2963
5704
  };
2964
5705
  var iosEntitlementsSource = (config) => {
2965
- const domains = config.deepLinkHosts.map((host) => ` <string>applinks:${escapeXml(host)}</string>`).join(`
5706
+ const domains = config.deepLinkHosts.map((host3) => ` <string>applinks:${escapeXml(host3)}</string>`).join(`
2966
5707
  `);
2967
5708
  return `<?xml version="1.0" encoding="UTF-8"?>
2968
5709
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -2977,10 +5718,10 @@ ${domains}
2977
5718
  `;
2978
5719
  };
2979
5720
  var configureIosEntitlements = async (config) => {
2980
- const path = join10(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
5721
+ const path = join15(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
2981
5722
  let current = "";
2982
5723
  try {
2983
- current = await readFile11(path, "utf8");
5724
+ current = await readFile14(path, "utf8");
2984
5725
  } catch (error) {
2985
5726
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2986
5727
  throw error;
@@ -2990,13 +5731,13 @@ var configureIosEntitlements = async (config) => {
2990
5731
  if (current === source)
2991
5732
  return false;
2992
5733
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2993
- await writeFile9(temporary, source, { flag: "wx" });
2994
- await rename9(temporary, path);
5734
+ await writeFile12(temporary, source, { flag: "wx" });
5735
+ await rename11(temporary, path);
2995
5736
  return true;
2996
5737
  };
2997
5738
  var configureIosProject = async (config) => {
2998
- const path = join10(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
2999
- const source = await readFile11(path, "utf8");
5739
+ const path = join15(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
5740
+ const source = await readFile14(path, "utf8");
3000
5741
  const declarations = [
3001
5742
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
3002
5743
  ].map((match) => match[1]);
@@ -3033,9 +5774,99 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
3033
5774
  changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
3034
5775
  };
3035
5776
  };
5777
+ // src/mobile/nativeDeviceCapabilities.ts
5778
+ import { readFile as readFile15, rename as rename12, writeFile as writeFile13 } from "fs/promises";
5779
+ import { join as join16 } from "path";
5780
+ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
5781
+ var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
5782
+ var NOT_FOUND2 = -1;
5783
+ var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5784
+ var writeChangedFile2 = async (path, source) => {
5785
+ const current = await readFile15(path, "utf8");
5786
+ if (current === source)
5787
+ return false;
5788
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
5789
+ await writeFile13(temporary, source, { flag: "wx" });
5790
+ await rename12(temporary, path);
5791
+ return true;
5792
+ };
5793
+ var managed = (source, region, insertion) => {
5794
+ const start = source.indexOf(START_MARKER2);
5795
+ const end = source.indexOf(END_MARKER2);
5796
+ if (start === NOT_FOUND2 !== (end === NOT_FOUND2) || start !== NOT_FOUND2 && end < start)
5797
+ throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
5798
+ if (start !== NOT_FOUND2) {
5799
+ const lineStart = source.lastIndexOf(`
5800
+ `, start) + 1;
5801
+ const nextLine = source.indexOf(`
5802
+ `, end + END_MARKER2.length);
5803
+ const lineEnd = nextLine === NOT_FOUND2 ? source.length : nextLine + 1;
5804
+ return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
5805
+ }
5806
+ if (region.length === 0)
5807
+ return source;
5808
+ if (insertion === NOT_FOUND2)
5809
+ throw new TypeError("Could not find a safe native project location for device permissions.");
5810
+ return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
5811
+ };
5812
+ var IOS_KEYS = {
5813
+ camera: "NSCameraUsageDescription",
5814
+ "location-always": "NSLocationAlwaysAndWhenInUseUsageDescription",
5815
+ "location-when-in-use": "NSLocationWhenInUseUsageDescription",
5816
+ "photo-library": "NSPhotoLibraryUsageDescription",
5817
+ "photo-library-add": "NSPhotoLibraryAddUsageDescription"
5818
+ };
5819
+ var iosDescription = (appName, purpose) => {
5820
+ if (purpose === "camera")
5821
+ return `${appName} uses your camera when you choose to take a photo.`;
5822
+ if (purpose === "photo-library")
5823
+ return `${appName} accesses your photo library only for photo actions you choose.`;
5824
+ if (purpose === "location-when-in-use")
5825
+ return `${appName} uses your location only while you are using the app and request a location-based action.`;
5826
+ if (purpose === "location-always")
5827
+ return `${appName} does not track location in the background; this description supports the foreground location provider required by the native runtime.`;
5828
+ return `${appName} adds to your photo library only for photo actions you choose.`;
5829
+ };
5830
+ var configureIos2 = async (config, plan) => {
5831
+ const path = join16(config.nativeProjectDirectory, "ios/App/App/Info.plist");
5832
+ const source = await readFile15(path, "utf8");
5833
+ const requirements = absoluteDeviceNativeRequirements(plan);
5834
+ const content = requirements.iosUsageDescriptions.map((purpose) => ` <key>${IOS_KEYS[purpose]}</key>
5835
+ <string>${escapeXml2(iosDescription(config.appName, purpose))}</string>`).join(`
5836
+ `);
5837
+ const region = content ? ` ${START_MARKER2}
5838
+ ${content}
5839
+ ${END_MARKER2}
5840
+ ` : "";
5841
+ return writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
5842
+ };
5843
+ var configureAndroid2 = async (config, plan) => {
5844
+ const path = join16(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
5845
+ const source = await readFile15(path, "utf8");
5846
+ const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
5847
+ const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
5848
+ `);
5849
+ const region = content ? ` ${START_MARKER2}
5850
+ ${content}
5851
+ ${END_MARKER2}
5852
+ ` : "";
5853
+ const application = source.indexOf("<application");
5854
+ const insertion = application === NOT_FOUND2 ? NOT_FOUND2 : source.lastIndexOf(`
5855
+ `, application) + 1;
5856
+ return writeChangedFile2(path, managed(source, region, insertion));
5857
+ };
5858
+ var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
5859
+ const results = await Promise.all(platforms.map(async (platform) => ({
5860
+ didChange: platform === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
5861
+ platform
5862
+ })));
5863
+ return {
5864
+ changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
5865
+ };
5866
+ };
3036
5867
  // src/mobile/releasePublisher.ts
3037
- import { access as access7 } from "fs/promises";
3038
- import { isAbsolute as isAbsolute4, relative as relative6, resolve as resolve10, sep as sep4 } from "path";
5868
+ import { access as access9 } from "fs/promises";
5869
+ import { isAbsolute as isAbsolute6, relative as relative10, resolve as resolve13, sep as sep6 } from "path";
3039
5870
  import { pathToFileURL as pathToFileURL3 } from "url";
3040
5871
  var prepareAbsoluteIosRelease = async (publisher, options) => {
3041
5872
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -3058,24 +5889,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
3058
5889
  }
3059
5890
  return versionCode;
3060
5891
  };
3061
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3062
- var isPublisher = (value) => isRecord7(value) && typeof value.publish === "function";
5892
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5893
+ var isPublisher = (value) => isRecord8(value) && typeof value.publish === "function";
3063
5894
  var publisherModulePath = (projectRoot, requested) => {
3064
- const root = resolve10(projectRoot);
3065
- const path = resolve10(root, requested);
3066
- const projectRelative = relative6(root, path);
3067
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep4}`) || isAbsolute4(projectRelative)) {
5895
+ const root = resolve13(projectRoot);
5896
+ const path = resolve13(root, requested);
5897
+ const projectRelative = relative10(root, path);
5898
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
3068
5899
  throw new TypeError("mobile publish --registry must remain inside the project.");
3069
5900
  }
3070
5901
  return path;
3071
5902
  };
3072
5903
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
3073
5904
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
3074
- await access7(modulePath).catch(() => {
5905
+ await access9(modulePath).catch(() => {
3075
5906
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
3076
5907
  });
3077
5908
  const loaded = await import(pathToFileURL3(modulePath).href);
3078
- const publisher = isRecord7(loaded) ? loaded.default ?? loaded.registry : undefined;
5909
+ const publisher = isRecord8(loaded) ? loaded.default ?? loaded.registry : undefined;
3079
5910
  if (!isPublisher(publisher)) {
3080
5911
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
3081
5912
  }
@@ -3132,67 +5963,112 @@ var publishAbsoluteIosRelease = async (options) => {
3132
5963
  return publication;
3133
5964
  };
3134
5965
  // src/mobile/routeMetadataTransform.ts
3135
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
3136
- import { dirname as dirname6, extname as extname2, relative as relative7, resolve as resolve11 } from "path";
3137
- import ts from "typescript";
5966
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
5967
+ import { dirname as dirname10, extname as extname4, relative as relative11, resolve as resolve14 } from "path";
5968
+ import ts2 from "typescript";
3138
5969
  var ROUTE_METHODS = new Set(["get", "head"]);
3139
5970
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
3140
- var PAGE_HANDLER = "handleReactPageRequest";
5971
+ var PAGE_HANDLERS = new Map([
5972
+ [
5973
+ "handleHTMLPageRequest",
5974
+ { framework: "html", inputKind: "static", propsProperty: "props" }
5975
+ ],
5976
+ [
5977
+ "handleHTMXPageRequest",
5978
+ { framework: "htmx", inputKind: "static", propsProperty: "props" }
5979
+ ],
5980
+ [
5981
+ "handleAngularPageRequest",
5982
+ {
5983
+ bundleProperty: "indexPath",
5984
+ framework: "angular",
5985
+ propsProperty: "requestContext",
5986
+ sourceProperty: "pagePath"
5987
+ }
5988
+ ],
5989
+ [
5990
+ "handleReactPageRequest",
5991
+ {
5992
+ bundleProperty: "index",
5993
+ framework: "react",
5994
+ pageProperty: "Page",
5995
+ propsProperty: "props"
5996
+ }
5997
+ ],
5998
+ [
5999
+ "handleSveltePageRequest",
6000
+ {
6001
+ bundleProperty: "indexPath",
6002
+ framework: "svelte",
6003
+ propsProperty: "props",
6004
+ sourceProperty: "pagePath"
6005
+ }
6006
+ ],
6007
+ [
6008
+ "handleVuePageRequest",
6009
+ {
6010
+ bundleProperty: "indexPath",
6011
+ framework: "vue",
6012
+ propsProperty: "props",
6013
+ sourceProperty: "pagePath"
6014
+ }
6015
+ ]
6016
+ ]);
3141
6017
  var posixPath = (value) => value.replace(/\\/g, "/");
3142
- var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname6(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
6018
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname10(entry), existsSync3, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
3143
6019
  var createProgram = (entry, projectRoot) => {
3144
6020
  const configPath = findTsconfig(entry, projectRoot);
3145
6021
  if (!configPath) {
3146
- return ts.createProgram([entry], {
6022
+ return ts2.createProgram([entry], {
3147
6023
  allowJs: true,
3148
- jsx: ts.JsxEmit.ReactJSX,
3149
- module: ts.ModuleKind.ESNext,
3150
- moduleResolution: ts.ModuleResolutionKind.Bundler,
3151
- target: ts.ScriptTarget.ESNext
6024
+ jsx: ts2.JsxEmit.ReactJSX,
6025
+ module: ts2.ModuleKind.ESNext,
6026
+ moduleResolution: ts2.ModuleResolutionKind.Bundler,
6027
+ target: ts2.ScriptTarget.ESNext
3152
6028
  });
3153
6029
  }
3154
- const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname6(configPath));
6030
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath, (path) => readFileSync5(path, "utf8")).config, ts2.sys, dirname10(configPath));
3155
6031
  if (!parsed.fileNames.includes(entry))
3156
6032
  parsed.fileNames.push(entry);
3157
- return ts.createProgram(parsed.fileNames, parsed.options);
6033
+ return ts2.createProgram(parsed.fileNames, parsed.options);
3158
6034
  };
3159
6035
  var propertyName = (property) => {
3160
6036
  if (!("name" in property) || !property.name)
3161
6037
  return;
3162
- if (ts.isIdentifier(property.name))
6038
+ if (ts2.isIdentifier(property.name))
3163
6039
  return property.name.text;
3164
- if (ts.isStringLiteralLike(property.name))
6040
+ if (ts2.isStringLiteralLike(property.name))
3165
6041
  return property.name.text;
3166
6042
  return;
3167
6043
  };
3168
- var objectPropertyExpression = (object, name) => {
3169
- const property = object.properties.find((candidate) => propertyName(candidate) === name);
3170
- if (property && ts.isPropertyAssignment(property)) {
6044
+ var objectPropertyExpression = (object3, name) => {
6045
+ const property = object3.properties.find((candidate) => propertyName(candidate) === name);
6046
+ if (property && ts2.isPropertyAssignment(property)) {
3171
6047
  return property.initializer;
3172
6048
  }
3173
- if (property && ts.isShorthandPropertyAssignment(property)) {
6049
+ if (property && ts2.isShorthandPropertyAssignment(property)) {
3174
6050
  return property.name;
3175
6051
  }
3176
6052
  return;
3177
6053
  };
3178
6054
  var serializeType = (type, checker, ancestors = new Set) => {
3179
- if (type.flags & ts.TypeFlags.Any)
6055
+ if (type.flags & ts2.TypeFlags.Any)
3180
6056
  return { type: "any" };
3181
- if (type.flags & ts.TypeFlags.Unknown)
6057
+ if (type.flags & ts2.TypeFlags.Unknown)
3182
6058
  return { type: "unknown" };
3183
- if (type.flags & ts.TypeFlags.Never)
6059
+ if (type.flags & ts2.TypeFlags.Never)
3184
6060
  return { type: "never" };
3185
- if (type.flags & ts.TypeFlags.StringLike)
6061
+ if (type.flags & ts2.TypeFlags.StringLike)
3186
6062
  return { type: "string" };
3187
- if (type.flags & ts.TypeFlags.NumberLike)
6063
+ if (type.flags & ts2.TypeFlags.NumberLike)
3188
6064
  return { type: "number" };
3189
- if (type.flags & ts.TypeFlags.BooleanLike)
6065
+ if (type.flags & ts2.TypeFlags.BooleanLike)
3190
6066
  return { type: "boolean" };
3191
- if (type.flags & ts.TypeFlags.BigIntLike)
6067
+ if (type.flags & ts2.TypeFlags.BigIntLike)
3192
6068
  return { type: "bigint" };
3193
- if (type.flags & ts.TypeFlags.Null)
6069
+ if (type.flags & ts2.TypeFlags.Null)
3194
6070
  return { type: "null" };
3195
- if (type.flags & ts.TypeFlags.Undefined)
6071
+ if (type.flags & ts2.TypeFlags.Undefined)
3196
6072
  return { type: "undefined" };
3197
6073
  if (type.isUnion()) {
3198
6074
  return {
@@ -3206,11 +6082,11 @@ var serializeType = (type, checker, ancestors = new Set) => {
3206
6082
  }
3207
6083
  if (ancestors.has(type)) {
3208
6084
  return {
3209
- ref: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
6085
+ ref: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
3210
6086
  };
3211
6087
  }
3212
6088
  ancestors.add(type);
3213
- const arrayElement = checker.getIndexTypeOfType(type, ts.IndexKind.Number);
6089
+ const arrayElement = checker.getIndexTypeOfType(type, ts2.IndexKind.Number);
3214
6090
  const properties = checker.getPropertiesOfType(type);
3215
6091
  let schema;
3216
6092
  if (arrayElement && properties.some(({ name }) => name === "length")) {
@@ -3225,7 +6101,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
3225
6101
  return [
3226
6102
  property.name,
3227
6103
  {
3228
- optional: Boolean(property.flags & ts.SymbolFlags.Optional),
6104
+ optional: Boolean(property.flags & ts2.SymbolFlags.Optional),
3229
6105
  schema: serializeType(propertyType, checker, ancestors)
3230
6106
  }
3231
6107
  ];
@@ -3233,7 +6109,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
3233
6109
  schema = { properties: Object.fromEntries(entries), type: "object" };
3234
6110
  } else {
3235
6111
  schema = {
3236
- type: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
6112
+ type: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
3237
6113
  };
3238
6114
  }
3239
6115
  ancestors.delete(type);
@@ -3251,25 +6127,25 @@ var pagePropsType = (pageExpression, propsExpression, checker) => {
3251
6127
  };
3252
6128
  var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
3253
6129
  let symbol = checker.getSymbolAtLocation(expression);
3254
- if (symbol?.flags && symbol.flags & ts.SymbolFlags.Alias) {
6130
+ if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) {
3255
6131
  symbol = checker.getAliasedSymbol(symbol);
3256
6132
  }
3257
6133
  const declaration = symbol?.declarations?.[0];
3258
6134
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
3259
6135
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
3260
- const source = posixPath(relative7(projectRoot, file));
6136
+ const source = posixPath(relative11(projectRoot, file));
3261
6137
  return `${source}#${exportedName}`;
3262
6138
  };
3263
6139
  var resolveAlias = (symbol, checker) => {
3264
- if (!(symbol.flags & ts.SymbolFlags.Alias))
6140
+ if (!(symbol.flags & ts2.SymbolFlags.Alias))
3265
6141
  return symbol;
3266
6142
  return checker.getAliasedSymbol(symbol);
3267
6143
  };
3268
6144
  var assetKey = (expression, checker, seen = new Set) => {
3269
6145
  if (!expression)
3270
6146
  return;
3271
- if (ts.isIdentifier(expression)) {
3272
- const unresolved = ts.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
6147
+ if (ts2.isIdentifier(expression)) {
6148
+ const unresolved = ts2.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
3273
6149
  if (!unresolved)
3274
6150
  return;
3275
6151
  const symbol = resolveAlias(unresolved, checker);
@@ -3277,28 +6153,123 @@ var assetKey = (expression, checker, seen = new Set) => {
3277
6153
  return;
3278
6154
  seen.add(symbol);
3279
6155
  const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
3280
- if (!declaration || !ts.isVariableDeclaration(declaration))
6156
+ if (!declaration || !ts2.isVariableDeclaration(declaration))
3281
6157
  return;
3282
6158
  return assetKey(declaration.initializer, checker, seen);
3283
6159
  }
3284
- if (!ts.isCallExpression(expression))
6160
+ if (!ts2.isCallExpression(expression))
3285
6161
  return;
3286
- if (!ts.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
6162
+ if (!ts2.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
3287
6163
  return;
3288
6164
  }
3289
6165
  const [, key] = expression.arguments;
3290
- return key && ts.isStringLiteralLike(key) ? key.text : undefined;
6166
+ return key && ts2.isStringLiteralLike(key) ? key.text : undefined;
6167
+ };
6168
+ var staticString = (expression, bindings) => {
6169
+ if (ts2.isStringLiteralLike(expression))
6170
+ return expression.text;
6171
+ if (ts2.isIdentifier(expression))
6172
+ return bindings.get(expression.text);
6173
+ if (ts2.isNoSubstitutionTemplateLiteral(expression))
6174
+ return expression.text;
6175
+ if (!ts2.isTemplateExpression(expression))
6176
+ return;
6177
+ let value = expression.head.text;
6178
+ for (const span of expression.templateSpans) {
6179
+ const substitution = staticString(span.expression, bindings);
6180
+ if (substitution === undefined)
6181
+ return;
6182
+ value += substitution + span.literal.text;
6183
+ }
6184
+ return value;
6185
+ };
6186
+ var assetKeyWithBindings = (expression, checker, bindings = new Map) => {
6187
+ if (!expression)
6188
+ return;
6189
+ if (ts2.isCallExpression(expression) && ts2.isIdentifier(expression.expression) && expression.expression.text === "asset") {
6190
+ const [, key] = expression.arguments;
6191
+ return key ? staticString(key, bindings) : undefined;
6192
+ }
6193
+ return assetKey(expression, checker);
6194
+ };
6195
+ var callableObject = (call, checker) => {
6196
+ const symbol = checker.getSymbolAtLocation(call.expression);
6197
+ const resolved = symbol ? resolveAlias(symbol, checker) : undefined;
6198
+ const declaration = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
6199
+ let callable;
6200
+ if (declaration && ts2.isFunctionDeclaration(declaration)) {
6201
+ callable = declaration;
6202
+ } else if (declaration && ts2.isVariableDeclaration(declaration) && declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer))) {
6203
+ callable = declaration.initializer;
6204
+ }
6205
+ if (!callable)
6206
+ return;
6207
+ const bindings = new Map;
6208
+ callable.parameters.forEach((parameter, index) => {
6209
+ if (!ts2.isIdentifier(parameter.name))
6210
+ return;
6211
+ const argument = call.arguments[index];
6212
+ if (!argument)
6213
+ return;
6214
+ const value = staticString(argument, new Map);
6215
+ if (value !== undefined)
6216
+ bindings.set(parameter.name.text, value);
6217
+ });
6218
+ const { body } = callable;
6219
+ if (!body)
6220
+ return;
6221
+ const expressionBody = ts2.isParenthesizedExpression(body) ? body.expression : body;
6222
+ if (ts2.isObjectLiteralExpression(expressionBody)) {
6223
+ return { bindings, object: expressionBody };
6224
+ }
6225
+ if (ts2.isBlock(body)) {
6226
+ const returned = body.statements.find(ts2.isReturnStatement)?.expression;
6227
+ if (returned && ts2.isObjectLiteralExpression(returned)) {
6228
+ return { bindings, object: returned };
6229
+ }
6230
+ }
6231
+ return;
6232
+ };
6233
+ var spreadObject = (expression, checker, bindings) => {
6234
+ if (ts2.isObjectLiteralExpression(expression)) {
6235
+ return { bindings, object: expression };
6236
+ }
6237
+ if (!ts2.isCallExpression(expression))
6238
+ return;
6239
+ return callableObject(expression, checker);
6240
+ };
6241
+ var objectAssetKey = (object3, name, checker, bindings = new Map) => {
6242
+ for (const property of [...object3.properties].reverse()) {
6243
+ if (propertyName(property) === name && ts2.isShorthandPropertyAssignment(property)) {
6244
+ return assetKeyWithBindings(property.name, checker, bindings);
6245
+ }
6246
+ if (propertyName(property) === name && ts2.isPropertyAssignment(property)) {
6247
+ return assetKeyWithBindings(property.initializer, checker, bindings);
6248
+ }
6249
+ if (!ts2.isSpreadAssignment(property))
6250
+ continue;
6251
+ const nestedObject = spreadObject(property.expression, checker, bindings);
6252
+ if (!nestedObject)
6253
+ continue;
6254
+ const nested = objectAssetKey(nestedObject.object, name, checker, nestedObject.bindings);
6255
+ if (nested)
6256
+ return nested;
6257
+ }
6258
+ return;
3291
6259
  };
3292
6260
  var findPageCall = (nodes) => {
3293
6261
  let found;
3294
6262
  const visit = (candidate) => {
3295
6263
  if (found)
3296
6264
  return;
3297
- if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && candidate.expression.text === PAGE_HANDLER) {
3298
- found = candidate;
6265
+ if (ts2.isCallExpression(candidate) && ts2.isIdentifier(candidate.expression) && PAGE_HANDLERS.has(candidate.expression.text)) {
6266
+ const definition = PAGE_HANDLERS.get(candidate.expression.text);
6267
+ if (!definition)
6268
+ return;
6269
+ found = { definition, node: candidate };
3299
6270
  return;
3300
6271
  }
3301
- ts.forEachChild(candidate, visit);
6272
+ ts2.forEachChild(candidate, visit);
3302
6273
  };
3303
6274
  for (const node of nodes)
3304
6275
  visit(node);
@@ -3307,37 +6278,74 @@ var findPageCall = (nodes) => {
3307
6278
  var isProjectSource = (sourceFile, resolvedFile, projectRoot) => !sourceFile.isDeclarationFile && !resolvedFile.includes("/node_modules/") && resolvedFile.startsWith(`${projectRoot}/`);
3308
6279
  var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
3309
6280
  const callee = node.expression;
3310
- if (!ts.isPropertyAccessExpression(callee))
6281
+ if (!ts2.isPropertyAccessExpression(callee))
3311
6282
  return;
3312
6283
  if (!ROUTE_METHODS.has(callee.name.text))
3313
6284
  return;
3314
6285
  const [routePath] = node.arguments;
3315
- if (!routePath || !ts.isStringLiteralLike(routePath))
6286
+ if (!routePath || !ts2.isStringLiteralLike(routePath))
3316
6287
  return;
3317
- const pageCall = findPageCall(node.arguments.slice(1));
6288
+ const foundPageCall = findPageCall(node.arguments.slice(1));
6289
+ const pageCall = foundPageCall?.node;
6290
+ const definition = foundPageCall?.definition;
3318
6291
  const [input] = pageCall?.arguments ?? [];
3319
- if (!pageCall || !input || !ts.isObjectLiteralExpression(input)) {
6292
+ if (!pageCall || !input) {
3320
6293
  return;
3321
6294
  }
3322
- const page = objectPropertyExpression(input, "Page");
3323
- if (!page)
6295
+ if (!definition)
6296
+ return;
6297
+ if (definition.inputKind === "static") {
6298
+ const bundleKey2 = assetKey(input, checker);
6299
+ if (!bundleKey2)
6300
+ return;
6301
+ const pageId2 = `${definition.framework}:${bundleKey2}`;
6302
+ const propsSchemaHash2 = hashAbsoluteMobilePropsSchema({
6303
+ properties: {},
6304
+ type: "object"
6305
+ });
6306
+ return {
6307
+ inputKind: "static",
6308
+ metadata: {
6309
+ bundleKey: bundleKey2,
6310
+ contract: `${definition.framework}:${pageId2}:${propsSchemaHash2}`,
6311
+ framework: definition.framework,
6312
+ pageId: pageId2,
6313
+ propsSchemaHash: propsSchemaHash2
6314
+ },
6315
+ pageCallStart: pageCall.getStart(sourceFile),
6316
+ routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
6317
+ };
6318
+ }
6319
+ if (!ts2.isObjectLiteralExpression(input) || !definition.bundleProperty) {
6320
+ return;
6321
+ }
6322
+ const page = definition.pageProperty ? objectPropertyExpression(input, definition.pageProperty) : undefined;
6323
+ const source = definition.sourceProperty ? objectAssetKey(input, definition.sourceProperty, checker) : undefined;
6324
+ if (definition.pageProperty && !page)
6325
+ return;
6326
+ if (definition.sourceProperty && !source)
3324
6327
  return;
3325
- const props = objectPropertyExpression(input, "props");
3326
- const index = objectPropertyExpression(input, "index");
3327
- const bundleKey = assetKey(index, checker);
6328
+ const props = objectPropertyExpression(input, definition.propsProperty);
6329
+ const bundleKey = objectAssetKey(input, definition.bundleProperty, checker);
3328
6330
  if (!bundleKey)
3329
6331
  return;
3330
- const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot);
3331
- const schema = serializeType(pagePropsType(page, props, checker), checker);
6332
+ const pageId = page ? resolvePageIdentity(page, sourceFile, checker, projectRoot) : `${definition.framework}:${source}`;
6333
+ let propsType;
6334
+ if (page)
6335
+ propsType = pagePropsType(page, props, checker);
6336
+ else if (props)
6337
+ propsType = checker.getTypeAtLocation(props);
6338
+ const schema = propsType ? serializeType(propsType, checker) : { properties: {}, type: "object" };
3332
6339
  const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
3333
6340
  const metadata = {
3334
6341
  bundleKey,
3335
- contract: `react:${pageId}:${propsSchemaHash}`,
3336
- framework: "react",
6342
+ contract: `${definition.framework}:${pageId}:${propsSchemaHash}`,
6343
+ framework: definition.framework,
3337
6344
  pageId,
3338
6345
  propsSchemaHash
3339
6346
  };
3340
6347
  const result = {
6348
+ inputKind: "object",
3341
6349
  metadata,
3342
6350
  pageCallStart: pageCall.getStart(sourceFile),
3343
6351
  routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
@@ -3350,21 +6358,21 @@ var analyzeSourceFile = (sourceFile, checker, projectRoot) => {
3350
6358
  byRouteCall: new Map
3351
6359
  };
3352
6360
  const visit = (node) => {
3353
- const result = ts.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
6361
+ const result = ts2.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
3354
6362
  if (result) {
3355
6363
  analysis.byPageCall.set(result.pageCallStart, result);
3356
6364
  analysis.byRouteCall.set(result.routeCallSpan, result);
3357
6365
  }
3358
- ts.forEachChild(node, visit);
6366
+ ts2.forEachChild(node, visit);
3359
6367
  };
3360
- ts.forEachChild(sourceFile, visit);
6368
+ ts2.forEachChild(sourceFile, visit);
3361
6369
  return analysis;
3362
6370
  };
3363
6371
  var analyzeProgram = (program, projectRoot) => {
3364
6372
  const checker = program.getTypeChecker();
3365
6373
  const analyzed = new Map;
3366
6374
  for (const sourceFile of program.getSourceFiles()) {
3367
- const resolvedFile = resolve11(sourceFile.fileName);
6375
+ const resolvedFile = resolve14(sourceFile.fileName);
3368
6376
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
3369
6377
  continue;
3370
6378
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -3373,34 +6381,44 @@ var analyzeProgram = (program, projectRoot) => {
3373
6381
  }
3374
6382
  return analyzed;
3375
6383
  };
3376
- var metadataExpression = (metadata) => ts.factory.createObjectLiteralExpression(Object.entries(metadata).map(([key, item]) => ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(key), ts.factory.createStringLiteral(item))), false);
6384
+ var metadataExpression = (metadata) => ts2.factory.createObjectLiteralExpression(Object.entries(metadata).map(([key, item]) => ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(key), ts2.factory.createStringLiteral(item))), false);
3377
6385
  var routeOptions = (existing, metadata) => {
3378
- const detail = ts.factory.createObjectLiteralExpression([
3379
- ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
6386
+ const detail = ts2.factory.createObjectLiteralExpression([
6387
+ ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
3380
6388
  ]);
3381
6389
  if (!existing) {
3382
- return ts.factory.createObjectLiteralExpression([
3383
- ts.factory.createPropertyAssignment("detail", detail)
6390
+ return ts2.factory.createObjectLiteralExpression([
6391
+ ts2.factory.createPropertyAssignment("detail", detail)
3384
6392
  ]);
3385
6393
  }
3386
- return ts.factory.createObjectLiteralExpression([
3387
- ts.factory.createSpreadAssignment(existing),
3388
- ts.factory.createPropertyAssignment("detail", ts.factory.createObjectLiteralExpression([
3389
- ts.factory.createSpreadAssignment(ts.factory.createPropertyAccessExpression(existing, "detail")),
3390
- ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
6394
+ return ts2.factory.createObjectLiteralExpression([
6395
+ ts2.factory.createSpreadAssignment(existing),
6396
+ ts2.factory.createPropertyAssignment("detail", ts2.factory.createObjectLiteralExpression([
6397
+ ts2.factory.createSpreadAssignment(ts2.factory.createPropertyAccessExpression(existing, "detail")),
6398
+ ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
3391
6399
  ]))
3392
6400
  ]);
3393
6401
  };
3394
6402
  var transformPageCall = (node, page) => {
3395
6403
  if (!page)
3396
6404
  return;
6405
+ if (page.inputKind === "static") {
6406
+ const [pagePath, existingOptions, ...rest] = node.arguments;
6407
+ if (!pagePath)
6408
+ return;
6409
+ const options = ts2.factory.createObjectLiteralExpression([
6410
+ ...existingOptions ? [ts2.factory.createSpreadAssignment(existingOptions)] : [],
6411
+ ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
6412
+ ]);
6413
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [pagePath, options, ...rest]);
6414
+ }
3397
6415
  const [input] = node.arguments;
3398
- if (!input || !ts.isObjectLiteralExpression(input))
6416
+ if (!input || !ts2.isObjectLiteralExpression(input))
3399
6417
  return;
3400
- return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
3401
- ts.factory.updateObjectLiteralExpression(input, [
6418
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [
6419
+ ts2.factory.updateObjectLiteralExpression(input, [
3402
6420
  ...input.properties,
3403
- ts.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
6421
+ ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
3404
6422
  ]),
3405
6423
  ...node.arguments.slice(1)
3406
6424
  ]);
@@ -3413,16 +6431,16 @@ var transformRouteCall = (node, route) => {
3413
6431
  return;
3414
6432
  const options = maybeHandler ? maybeOptions : undefined;
3415
6433
  const handler = maybeHandler ?? maybeOptions;
3416
- return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
6434
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
3417
6435
  };
3418
6436
  var transformFile = (source, fileName, analysis) => {
3419
- const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
6437
+ const sourceFile = ts2.createSourceFile(fileName, source, ts2.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS);
3420
6438
  const transformer = (context) => {
3421
6439
  const visit = (node) => {
3422
- if (!ts.isCallExpression(node)) {
3423
- return ts.visitEachChild(node, visit, context);
6440
+ if (!ts2.isCallExpression(node)) {
6441
+ return ts2.visitEachChild(node, visit, context);
3424
6442
  }
3425
- const transformedChildren = ts.visitEachChild(node, visit, context);
6443
+ const transformedChildren = ts2.visitEachChild(node, visit, context);
3426
6444
  const page = analysis.byPageCall.get(node.getStart(sourceFile));
3427
6445
  const transformedPage = transformPageCall(transformedChildren, page);
3428
6446
  if (transformedPage)
@@ -3433,81 +6451,120 @@ var transformFile = (source, fileName, analysis) => {
3433
6451
  return transformedRoute;
3434
6452
  return transformedChildren;
3435
6453
  };
3436
- return (node) => ts.visitNode(node, visit, ts.isSourceFile) ?? node;
6454
+ return (node) => ts2.visitNode(node, visit, ts2.isSourceFile) ?? node;
3437
6455
  };
3438
- const result = ts.transform(sourceFile, [transformer]);
6456
+ const result = ts2.transform(sourceFile, [transformer]);
3439
6457
  try {
3440
6458
  const [transformed] = result.transformed;
3441
6459
  if (!transformed)
3442
6460
  throw new TypeError("Mobile route transform failed.");
3443
- return ts.createPrinter().printFile(transformed);
6461
+ return ts2.createPrinter().printFile(transformed);
3444
6462
  } finally {
3445
6463
  result.dispose();
3446
6464
  }
3447
6465
  };
3448
6466
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
3449
6467
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
3450
- const projectRoot = resolve11(options.projectRoot ?? process.cwd());
3451
- const entry = resolve11(options.entry);
6468
+ const projectRoot = resolve14(options.projectRoot ?? process.cwd());
6469
+ const entry = resolve14(options.entry);
3452
6470
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
3453
6471
  return {
3454
6472
  name: "absolute-mobile-route-metadata",
3455
6473
  setup(build) {
3456
6474
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
3457
- const analysis = analyzed.get(resolve11(path));
6475
+ const analysis = analyzed.get(resolve14(path));
3458
6476
  if (!analysis)
3459
6477
  return;
3460
6478
  const source = await Bun.file(path).text();
3461
6479
  return {
3462
6480
  contents: transformFile(source, path, analysis),
3463
- loader: extname2(path).endsWith("x") ? "tsx" : "ts"
6481
+ loader: extname4(path).endsWith("x") ? "tsx" : "ts"
3464
6482
  };
3465
6483
  });
3466
6484
  }
3467
6485
  };
3468
6486
  };
3469
6487
  var inspectAbsoluteMobileRouteMetadata = (options) => {
3470
- const projectRoot = resolve11(options.projectRoot ?? process.cwd());
3471
- const entry = resolve11(options.entry);
6488
+ const projectRoot = resolve14(options.projectRoot ?? process.cwd());
6489
+ const entry = resolve14(options.entry);
3472
6490
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
3473
6491
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
3474
- file: posixPath(relative7(projectRoot, file)),
6492
+ file: posixPath(relative11(projectRoot, file)),
3475
6493
  metadata
3476
6494
  })));
3477
6495
  };
3478
6496
  export {
3479
6497
  writeAbsoluteCapacitorConfig,
6498
+ waitForAbsoluteIosHmrLog,
3480
6499
  verifyAbsoluteMobileCompatibilityProducer,
3481
6500
  verifyAbsoluteMobileAssociationFiles,
6501
+ validateAbsoluteSshDestination,
6502
+ validateAbsoluteRemoteMacProfileName,
6503
+ syncAbsoluteRemoteMacProject,
6504
+ startAbsoluteRemoteIosDevSession,
6505
+ startAbsoluteIosDevSession,
6506
+ serializeAbsoluteMobileAuthEnvironment,
3482
6507
  runWithAbsoluteMobileProducer,
3483
6508
  retainAbsoluteMobileCompatibilityArtifacts,
3484
6509
  resolveAbsoluteMobileRoute,
6510
+ resolveAbsoluteMobileNavigation,
3485
6511
  resolveAbsoluteMobileDeepLink,
3486
6512
  resolveAbsoluteMobileCompatibilityRelease,
6513
+ resolveAbsoluteMobileAuthManifest,
6514
+ resolveAbsoluteDeviceCapabilityPlan,
6515
+ repairAbsoluteIosDevSession,
6516
+ removeAbsoluteRemoteMacProfile,
6517
+ redactAbsoluteIosLog,
3487
6518
  readAbsoluteMobileMaterializedReleases,
3488
6519
  publishAbsoluteIosRelease,
3489
6520
  publishAbsoluteAndroidRelease,
6521
+ projectUsesAbsoluteSync,
6522
+ projectUsesAbsoluteAuth,
3490
6523
  prepareAbsoluteIosRelease,
6524
+ prepareAbsoluteIosDevProject,
3491
6525
  prepareAbsoluteAndroidRelease,
6526
+ parseIosSimulators,
6527
+ parseIosRuntimes,
6528
+ parseIosDeviceTypes,
3492
6529
  parseAbsoluteMobilePageRequest,
3493
6530
  parseAbsoluteMobilePageEnvelope,
3494
6531
  parseAbsoluteMobileCompatibilityArtifact,
3495
6532
  parseAbsoluteMobileBuildPageMetadata,
6533
+ parseAbsoluteIosLogLine,
6534
+ parseAbsoluteIosHmrLog,
6535
+ pairAbsoluteRemoteMac,
3496
6536
  normalizeAbsoluteMobileConfig,
3497
6537
  navigateAbsoluteMobilePage,
6538
+ missingAbsoluteDeviceCapabilityPackages,
6539
+ materializeAbsoluteRemoteMacAgent,
3498
6540
  materializeAbsoluteMobileCompatibilityBundle,
3499
6541
  materializeAbsoluteMobileAssociationFiles,
3500
6542
  materializeAbsoluteCapacitorWebBundle,
3501
6543
  matchesAbsoluteMobileRoutePattern,
3502
6544
  loadAbsoluteNativeReleasePublisher,
3503
6545
  loadAbsoluteMobileMaterializedBundle,
6546
+ loadAbsoluteDeviceCapabilityProviders,
6547
+ listAbsoluteRemoteMacProfiles,
6548
+ isAbsoluteIosNativeRootInput,
6549
+ installAbsoluteRemoteMacAgent,
6550
+ installAbsoluteMobileSyncRemediation,
6551
+ installAbsoluteMobileAuthEnvironment,
6552
+ inspectAbsoluteRemoteMac,
3504
6553
  inspectAbsoluteMobileRouteMetadata,
3505
6554
  hashAbsoluteMobilePropsSchema,
3506
6555
  getCurrentAbsoluteMobileProducerContext,
6556
+ getAbsoluteRemoteMacProfile,
6557
+ getAbsoluteMobileSyncRemediation,
3507
6558
  fingerprintAbsoluteIosNativeProject,
6559
+ fingerprintAbsoluteIosDevProject,
3508
6560
  finalizeAbsoluteMobilePage,
3509
6561
  finalizeAbsoluteMobileCompatibilityBuild,
3510
6562
  fetchAbsoluteMobilePage,
6563
+ disposeAbsoluteMobilePage,
6564
+ discoverAbsoluteSyncSchema,
6565
+ discoverAbsoluteDeviceCapabilities,
6566
+ directAbsoluteProjectPackages,
6567
+ createAbsoluteRemoteIosDevProject,
3511
6568
  createAbsoluteMobileUpgradeResponse,
3512
6569
  createAbsoluteMobileRouteMetadataPlugin,
3513
6570
  createAbsoluteMobilePageRequest,
@@ -3517,20 +6574,32 @@ export {
3517
6574
  createAbsoluteMobileCompatibilityDispatcher,
3518
6575
  createAbsoluteMobileCompatibilityArtifact,
3519
6576
  createAbsoluteMobileBlobArtifactStore,
6577
+ createAbsoluteMobileAuthManifest,
3520
6578
  createAbsoluteMobileAssociationPlugin,
3521
6579
  createAbsoluteMobileAssociationDocuments,
6580
+ createAbsoluteIosNativeWatcher,
3522
6581
  carryForwardAbsoluteMobileCompatibilityReleases,
3523
6582
  captureAbsoluteMobileRouteGraph,
3524
6583
  buildAbsoluteMobileCompatibilityRelease,
3525
6584
  buildAbsoluteIosRelease,
3526
6585
  buildAbsoluteAndroidRelease,
6586
+ assertAbsoluteDeviceCapabilityPackages,
6587
+ applyAbsoluteNativeDeviceCapabilities,
3527
6588
  applyAbsoluteNativeDeepLinks,
3528
6589
  activateAbsoluteMobilePage,
3529
6590
  acceptsAbsoluteMobilePage,
6591
+ absoluteRemoteProjectSyncCommands,
6592
+ absoluteRemoteMacSshBase,
6593
+ absoluteDeviceNativeRequirements,
3530
6594
  MOBILE_PAGE_REQUEST_HEADERS,
3531
6595
  AbsoluteMobilePageProtocolError,
3532
6596
  APPLE_ASSOCIATION_PATH,
3533
6597
  ANDROID_ASSOCIATION_PATH,
6598
+ ABSOLUTE_SYNC_PACKAGE,
6599
+ ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
6600
+ ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
6601
+ ABSOLUTE_NATIVE_AUTH_SCOPES,
6602
+ ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
3534
6603
  ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
3535
6604
  ABSOLUTE_MOBILE_ROUTE_DETAIL,
3536
6605
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
@@ -3539,9 +6608,11 @@ export {
3539
6608
  ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
3540
6609
  ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
3541
6610
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
6611
+ ABSOLUTE_IOS_SIMULATOR_NAME,
3542
6612
  ABSOLUTE_IOS_RELEASE_FORMAT,
6613
+ ABSOLUTE_AUTH_PACKAGE,
3543
6614
  ABSOLUTE_ANDROID_RELEASE_FORMAT
3544
6615
  };
3545
6616
 
3546
- //# debugId=8F6E00755BF98A2E64756E2164756E21
6617
+ //# debugId=1579CD5E4A79E32964756E2164756E21
3547
6618
  //# sourceMappingURL=index.js.map