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

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 (72) hide show
  1. package/README.md +135 -0
  2. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  3. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  4. package/dist/angular/index.js +341 -22
  5. package/dist/angular/index.js.map +8 -5
  6. package/dist/angular/server.js +341 -22
  7. package/dist/angular/server.js.map +8 -5
  8. package/dist/build.js +1254 -598
  9. package/dist/build.js.map +16 -13
  10. package/dist/cli/index.js +4995 -1614
  11. package/dist/dev/client/cssUtils.ts +16 -2
  12. package/dist/dev/client/handlers/rebuild.ts +11 -1
  13. package/dist/dev/client/hmrClient.ts +9 -3
  14. package/dist/dev/client/hmrTiming.ts +14 -7
  15. package/dist/dev/client/syncDevtools.ts +237 -0
  16. package/dist/index.js +1628 -862
  17. package/dist/index.js.map +24 -21
  18. package/dist/mobile/browser.js +123 -1
  19. package/dist/mobile/browser.js.map +6 -4
  20. package/dist/mobile/index.js +3361 -298
  21. package/dist/mobile/index.js.map +27 -13
  22. package/dist/mobile/remoteMacAgentEntry.js +29 -0
  23. package/dist/mobile/shellAuth.js +35 -0
  24. package/dist/mobile/shellBootstrap.js +585 -0
  25. package/dist/mobile/shellSync.js +123 -0
  26. package/dist/src/angular/pageHandler.d.ts +3 -0
  27. package/dist/src/build/pwa.d.ts +16 -0
  28. package/dist/src/cli/config/server.d.ts +1 -1
  29. package/dist/src/core/pageHandlers.d.ts +11 -2
  30. package/dist/src/core/prepare.d.ts +6 -0
  31. package/dist/src/dev/clientManager.d.ts +2 -0
  32. package/dist/src/mobile/androidEmulatorController.d.ts +6 -1
  33. package/dist/src/mobile/browser.d.ts +1 -0
  34. package/dist/src/mobile/buildPipeline.d.ts +1 -0
  35. package/dist/src/mobile/capacitorBundle.d.ts +22 -1
  36. package/dist/src/mobile/client.d.ts +4 -0
  37. package/dist/src/mobile/deviceCapabilities.d.ts +33 -0
  38. package/dist/src/mobile/index.d.ts +9 -0
  39. package/dist/src/mobile/iosConformance.d.ts +15 -0
  40. package/dist/src/mobile/iosNativeWatcher.d.ts +19 -0
  41. package/dist/src/mobile/iosRelease.d.ts +2 -2
  42. package/dist/src/mobile/iosSimulatorController.d.ts +89 -0
  43. package/dist/src/mobile/nativeAuth.d.ts +17 -0
  44. package/dist/src/mobile/nativeBackgroundSync.d.ts +4 -0
  45. package/dist/src/mobile/nativeDeviceCapabilities.d.ts +6 -0
  46. package/dist/src/mobile/releaseArtifact.d.ts +2 -0
  47. package/dist/src/mobile/remoteMacAgent.d.ts +2 -0
  48. package/dist/src/mobile/remoteMacAgentEntry.d.ts +1 -0
  49. package/dist/src/mobile/remoteMacProtocol.d.ts +114 -0
  50. package/dist/src/mobile/remoteMacWire.d.ts +2 -0
  51. package/dist/src/mobile/shellAuth.d.ts +13 -0
  52. package/dist/src/mobile/shellBootstrap.d.ts +18 -1
  53. package/dist/src/mobile/shellSync.d.ts +19 -0
  54. package/dist/src/mobile/staticDocument.d.ts +5 -0
  55. package/dist/src/mobile/syncRemediation.d.ts +10 -0
  56. package/dist/src/mobile/syncSchema.d.ts +9 -0
  57. package/dist/src/mobile/transport.d.ts +16 -1
  58. package/dist/src/plugins/hmr.d.ts +3 -0
  59. package/dist/src/plugins/imageOptimizer.d.ts +1 -1
  60. package/dist/src/svelte/pageHandler.d.ts +3 -0
  61. package/dist/src/utils/loadConfig.d.ts +1 -0
  62. package/dist/src/vue/pageHandler.d.ts +3 -0
  63. package/dist/svelte/index.js +312 -23
  64. package/dist/svelte/index.js.map +7 -4
  65. package/dist/svelte/server.js +307 -18
  66. package/dist/svelte/server.js.map +7 -4
  67. package/dist/types/build.d.ts +14 -0
  68. package/dist/vue/index.js +312 -23
  69. package/dist/vue/index.js.map +7 -4
  70. package/dist/vue/server.js +307 -18
  71. package/dist/vue/server.js.map +7 -4
  72. package/package.json +29 -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,269 @@ 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
+ "photo-library",
5076
+ "photo-library-add"
5077
+ ]);
5078
+ var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5079
+ var readJson = (path) => {
5080
+ const value = JSON.parse(readFileSync4(path, "utf8"));
5081
+ if (!object2(value))
5082
+ throw new TypeError(`${path} must contain an object.`);
5083
+ return value;
5084
+ };
5085
+ var text = (value, field) => {
5086
+ if (typeof value !== "string" || value.length === 0)
5087
+ throw new TypeError(`${field} must be a non-empty string.`);
5088
+ return value;
5089
+ };
5090
+ var androidPermissions = (value, field) => {
5091
+ if (value === undefined)
5092
+ return;
5093
+ if (!object2(value))
5094
+ throw new TypeError(`${field} must be an object.`);
5095
+ const { permissions } = value;
5096
+ if (!Array.isArray(permissions) || !permissions.every((permission) => typeof permission === "string" && ANDROID_PERMISSION_PATTERN.test(permission)))
5097
+ throw new TypeError(`${field}.permissions must contain Android permission names.`);
5098
+ return [...permissions];
5099
+ };
5100
+ var iosUsageDescriptions = (value, field) => {
5101
+ if (value === undefined)
5102
+ return;
5103
+ if (!object2(value))
5104
+ throw new TypeError(`${field} must be an object.`);
5105
+ const { usageDescriptions } = value;
5106
+ if (!Array.isArray(usageDescriptions) || !usageDescriptions.every((purpose) => typeof purpose === "string" && IOS_USAGE_DESCRIPTIONS.has(purpose)))
5107
+ throw new TypeError(`${field}.usageDescriptions contains an unsupported purpose.`);
5108
+ return [...usageDescriptions];
5109
+ };
5110
+ var parseProvider = (name, value) => {
5111
+ if (!IDENTIFIER_PATTERN.test(name))
5112
+ throw new TypeError("Device capability names must be identifiers.");
5113
+ if (!object2(value))
5114
+ throw new TypeError(`Device capability ${name} must be an object.`);
5115
+ const factory = text(value.factory, `${name}.factory`);
5116
+ const module = text(value.module, `${name}.module`);
5117
+ if (!IDENTIFIER_PATTERN.test(factory))
5118
+ throw new TypeError(`${name}.factory must be a JavaScript identifier.`);
5119
+ if (!CAPACITOR_MODULE_PATTERN.test(module))
5120
+ throw new TypeError(`${name}.module must be an official devices-capacitor subpath.`);
5121
+ if (!Array.isArray(value.packages) || !value.packages.every((spec) => typeof spec === "string" && CAPACITOR_PACKAGE_PATTERN.test(spec)))
5122
+ throw new TypeError(`${name}.packages must contain exact official Capacitor package versions.`);
5123
+ let native;
5124
+ const { native: nativeMetadata } = value;
5125
+ if (nativeMetadata !== undefined) {
5126
+ if (!object2(nativeMetadata))
5127
+ throw new TypeError(`${name}.native must be an object.`);
5128
+ const { android, ios } = nativeMetadata;
5129
+ const permissions = androidPermissions(android, `${name}.native.android`);
5130
+ const usageDescriptions = iosUsageDescriptions(ios, `${name}.native.ios`);
5131
+ native = {
5132
+ ...permissions === undefined ? {} : { android: { permissions } },
5133
+ ...usageDescriptions === undefined ? {} : { ios: { usageDescriptions } }
5134
+ };
5135
+ }
5136
+ return {
5137
+ factory,
5138
+ module,
5139
+ ...native === undefined ? {} : { native },
5140
+ packages: [...value.packages]
5141
+ };
5142
+ };
5143
+ var absoluteDeviceNativeRequirements = (plan) => ({
5144
+ androidPermissions: [
5145
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.android?.permissions ?? []))
5146
+ ].sort(),
5147
+ iosUsageDescriptions: [
5148
+ ...new Set(plan.capabilities.flatMap((name) => plan.providers[name]?.native?.ios?.usageDescriptions ?? []))
5149
+ ].sort()
5150
+ });
5151
+ var loadAbsoluteDeviceCapabilityProviders = (projectRoot) => {
5152
+ const path = join13(resolve11(projectRoot), "node_modules", CAPACITOR_ADAPTER, "package.json");
5153
+ const manifest = readJson(path);
5154
+ const { absolutejs } = manifest;
5155
+ const devices = object2(absolutejs) ? absolutejs.devices : undefined;
5156
+ if (!object2(devices) || devices.format !== 1 || devices.provider !== "capacitor" || !object2(devices.capabilities))
5157
+ throw new TypeError(`${CAPACITOR_ADAPTER} does not publish supported capability metadata.`);
5158
+ const entries = Object.entries(devices.capabilities).map(([name, provider]) => ({
5159
+ name,
5160
+ provider: parseProvider(name, provider)
5161
+ }));
5162
+ return Object.fromEntries(entries.sort((left, right) => left.name.localeCompare(right.name)).map(({ name, provider }) => [name, provider]));
5163
+ };
5164
+ var isIgnored = (path) => path.split("/").some((segment) => IGNORED_DIRECTORIES.has(segment));
5165
+ var importedCapabilities = (source, file) => {
5166
+ const names = new Set;
5167
+ const namespaces = new Set;
5168
+ const visit = (node) => {
5169
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && !node.importClause?.isTypeOnly) {
5170
+ const bindings = node.importClause?.namedBindings;
5171
+ if (bindings && ts.isNamedImports(bindings)) {
5172
+ for (const element of bindings.elements)
5173
+ if (!element.isTypeOnly)
5174
+ names.add((element.propertyName ?? element.name).text);
5175
+ }
5176
+ if (bindings && ts.isNamespaceImport(bindings))
5177
+ namespaces.add(bindings.name.text);
5178
+ }
5179
+ if (ts.isExportDeclaration(node) && !node.isTypeOnly && node.moduleSpecifier !== undefined && ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === DEVICES_PACKAGE && node.exportClause && ts.isNamedExports(node.exportClause)) {
5180
+ for (const element of node.exportClause.elements)
5181
+ if (!element.isTypeOnly)
5182
+ names.add((element.propertyName ?? element.name).text);
5183
+ }
5184
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && namespaces.has(node.expression.text))
5185
+ names.add(node.name.text);
5186
+ ts.forEachChild(node, visit);
5187
+ };
5188
+ const extension = extname3(file).toLowerCase();
5189
+ const sources = extension === ".svelte" || extension === ".vue" ? [...source.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script\s*>/giu)].map((match) => match[1]).filter((value) => value !== undefined) : [source];
5190
+ for (const [index, script] of sources.entries())
5191
+ visit(ts.createSourceFile(`${file}#script-${index}`, script, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX));
5192
+ return names;
5193
+ };
5194
+ var assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
5195
+ const missing = missingAbsoluteDeviceCapabilityPackages(plan, directAbsoluteProjectPackages(projectRoot));
5196
+ const mismatched = plan.requiredPackages.filter((spec) => {
5197
+ const separator = spec.lastIndexOf("@");
5198
+ const packageName = spec.slice(0, separator);
5199
+ if (missing.includes(spec))
5200
+ return false;
5201
+ try {
5202
+ return readJson(join13(resolve11(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
5203
+ } catch {
5204
+ return true;
5205
+ }
5206
+ });
5207
+ const unmet = [...missing, ...mismatched];
5208
+ if (unmet.length > 0)
5209
+ throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
5210
+ };
5211
+ var directAbsoluteProjectPackages = (projectRoot) => {
5212
+ const manifest = readJson(join13(resolve11(projectRoot), "package.json"));
5213
+ const packages = new Set;
5214
+ for (const field of ["dependencies", "devDependencies"]) {
5215
+ const dependencies = manifest[field];
5216
+ if (object2(dependencies))
5217
+ for (const name of Object.keys(dependencies))
5218
+ packages.add(name);
5219
+ }
5220
+ return packages;
5221
+ };
5222
+ var discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
5223
+ const root = resolve11(projectRoot);
5224
+ const known = new Set(Object.keys(providers));
5225
+ const capabilities = new Set;
5226
+ for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
5227
+ const portable = relative9(root, resolve11(root, path)).replaceAll("\\", "/");
5228
+ if (isIgnored(portable))
5229
+ continue;
5230
+ const source = readFileSync4(resolve11(root, portable), "utf8");
5231
+ for (const name of importedCapabilities(source, portable))
5232
+ if (known.has(name))
5233
+ capabilities.add(name);
5234
+ }
5235
+ return [...capabilities].sort();
5236
+ };
5237
+ var missingAbsoluteDeviceCapabilityPackages = (plan, directPackages) => plan.requiredPackages.filter((spec) => {
5238
+ const packageName = spec.slice(0, spec.lastIndexOf("@"));
5239
+ return !directPackages.has(packageName);
5240
+ });
5241
+ var resolveAbsoluteDeviceCapabilityPlan = (projectRoot) => {
5242
+ const allProviders = loadAbsoluteDeviceCapabilityProviders(projectRoot);
5243
+ const capabilities = discoverAbsoluteDeviceCapabilities(projectRoot, allProviders);
5244
+ const providers = {};
5245
+ for (const name of capabilities) {
5246
+ const provider = allProviders[name];
5247
+ if (provider)
5248
+ providers[name] = provider;
5249
+ }
5250
+ return {
5251
+ capabilities,
5252
+ providers,
5253
+ requiredPackages: [
5254
+ ...new Set(capabilities.flatMap((name) => providers[name]?.packages ?? []))
5255
+ ].sort()
5256
+ };
5257
+ };
5258
+
2717
5259
  // src/mobile/buildPipeline.ts
2718
5260
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
2719
5261
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
@@ -2724,12 +5266,11 @@ var serverExportName = (loaded, app) => {
2724
5266
  return "app";
2725
5267
  return "default";
2726
5268
  };
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;
5269
+ var restoreEnvironmentVariable = (name, previous) => {
5270
+ if (previous !== undefined)
5271
+ process.env[name] = previous;
5272
+ else
5273
+ delete process.env[name];
2733
5274
  };
2734
5275
  var requireRelease = (releases, releaseId) => {
2735
5276
  const release = releases.get(releaseId);
@@ -2749,11 +5290,11 @@ var loadServerApp = async (producerPath) => {
2749
5290
  return { app, exportName };
2750
5291
  };
2751
5292
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2752
- const buildDirectory = resolve9(options.buildDirectory);
5293
+ const buildDirectory = resolve12(options.buildDirectory);
2753
5294
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
2754
- const root = join9(buildDirectory, ".absolutejs", "mobile-compatibility");
5295
+ const root = join14(buildDirectory, ".absolutejs", "mobile-compatibility");
2755
5296
  const [manifestSource, previous] = await Promise.all([
2756
- readFile10(join9(buildDirectory, "manifest.json"), "utf8"),
5297
+ readFile13(join14(buildDirectory, "manifest.json"), "utf8"),
2757
5298
  readAbsoluteMobileMaterializedReleases(root)
2758
5299
  ]);
2759
5300
  const manifest = JSON.parse(manifestSource);
@@ -2761,12 +5302,20 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2761
5302
  throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
2762
5303
  }
2763
5304
  const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
5305
+ const previousCompiledRuntime = process.env.ABSOLUTE_COMPILED_RUNTIME;
5306
+ const previousConfigPath = process.env.ABSOLUTE_CONFIG;
2764
5307
  process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
5308
+ process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
5309
+ if (options.configPath) {
5310
+ process.env.ABSOLUTE_CONFIG = resolve12(options.projectRoot, options.configPath);
5311
+ }
2765
5312
  let loaded;
2766
5313
  try {
2767
- loaded = await loadServerApp(resolve9(options.producerPath));
5314
+ loaded = await loadServerApp(resolve12(options.producerPath));
2768
5315
  } finally {
2769
- restoreBuildDirectory(previousBuildDirectory);
5316
+ restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
5317
+ restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
5318
+ restoreEnvironmentVariable("ABSOLUTE_CONFIG", previousConfigPath);
2770
5319
  }
2771
5320
  const current = await buildAbsoluteMobileCompatibilityRelease({
2772
5321
  app: loaded.app,
@@ -2775,9 +5324,17 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2775
5324
  manifest,
2776
5325
  previousArtifacts: previous.map(({ artifact }) => artifact),
2777
5326
  producerExport: loaded.exportName,
2778
- producerPath: resolve9(options.producerPath),
5327
+ producerPath: resolve12(options.producerPath),
2779
5328
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
2780
5329
  });
5330
+ const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
5331
+ const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
5332
+ const syncSchema = sync ? discoverAbsoluteSyncSchema(options.projectRoot) : undefined;
5333
+ const deviceCapabilities = resolveAbsoluteDeviceCapabilityPlan(options.projectRoot);
5334
+ assertAbsoluteDeviceCapabilityPackages(options.projectRoot, deviceCapabilities);
5335
+ if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
5336
+ 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.");
5337
+ }
2781
5338
  const releasesById = new Map([current, ...previous].map((release) => [
2782
5339
  release.artifact.releaseId,
2783
5340
  release
@@ -2790,11 +5347,127 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2790
5347
  });
2791
5348
  await materializeAbsoluteCapacitorWebBundle({
2792
5349
  artifact: current.artifact,
5350
+ ...auth ? { auth } : {},
2793
5351
  buildDirectory,
2794
- config: mobile
5352
+ config: mobile,
5353
+ deviceCapabilities,
5354
+ projectRoot: options.projectRoot,
5355
+ ...sync ? { sync: true } : {},
5356
+ ...syncSchema ? { syncSchema: { components: syncSchema.components } } : {}
2795
5357
  });
2796
5358
  return current.artifact;
2797
5359
  };
5360
+
5361
+ // src/mobile/index.ts
5362
+ init_syncSchema();
5363
+
5364
+ // node_modules/@absolutejs/sync/dist/client/runtimeTransport.js
5365
+ var RUNTIME_TRANSPORT2 = Symbol.for("@absolutejs/sync/client-runtime-transport");
5366
+ var host2 = globalThis;
5367
+ var isRegistry2 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations")) && Array.isArray(Reflect.get(value, "clients"));
5368
+ var registry2 = (() => {
5369
+ const existing = host2[RUNTIME_TRANSPORT2];
5370
+ if (isRegistry2(existing))
5371
+ return existing;
5372
+ if (typeof existing === "object" && existing !== null && Array.isArray(Reflect.get(existing, "installations"))) {
5373
+ Reflect.set(existing, "clients", []);
5374
+ return existing;
5375
+ }
5376
+ const created = { clients: [], installations: [] };
5377
+ Object.defineProperty(host2, RUNTIME_TRANSPORT2, {
5378
+ configurable: false,
5379
+ enumerable: false,
5380
+ value: created,
5381
+ writable: false
5382
+ });
5383
+ return created;
5384
+ })();
5385
+ var maximum = (values) => {
5386
+ const present = values.filter((value) => value !== undefined);
5387
+ return present.length === 0 ? undefined : Math.max(...present);
5388
+ };
5389
+ var minimum = (values) => {
5390
+ const present = values.filter((value) => value !== undefined);
5391
+ return present.length === 0 ? undefined : Math.min(...present);
5392
+ };
5393
+ var inspectSyncRuntime = async () => {
5394
+ const clients = [...registry2.clients];
5395
+ const statuses = clients.map((client) => client.status());
5396
+ const deadLetters = (await Promise.all(clients.map((client) => client.listDeadLetters()))).flat().map((record) => ({
5397
+ attempts: record.attempts,
5398
+ ...record.rejection?.code ? { code: record.rejection.code } : {},
5399
+ createdAt: record.createdAt,
5400
+ ...record.deadLetteredAt === undefined ? {} : { deadLetteredAt: record.deadLetteredAt },
5401
+ ...record.rejection?.kind ? { kind: record.rejection.kind } : {},
5402
+ ...record.rejection?.message ? { message: record.rejection.message } : {},
5403
+ name: record.name,
5404
+ operationId: record.operationId
5405
+ })).sort((left, right) => (left.deadLetteredAt ?? left.createdAt) - (right.deadLetteredAt ?? right.createdAt) || left.operationId.localeCompare(right.operationId));
5406
+ const lastError = statuses.findLast((status) => status.lastError)?.lastError;
5407
+ return {
5408
+ automaticResolutions: statuses.reduce((total, status) => total + status.automaticResolutions, 0),
5409
+ clients: clients.length,
5410
+ conflicts: deadLetters.filter((record) => record.kind === "conflict").length,
5411
+ deadLetters,
5412
+ ...lastError ? { lastError } : {},
5413
+ ...maximum(statuses.map((status) => status.lastSuccessfulPullAt)) === undefined ? {} : {
5414
+ lastSuccessfulPullAt: maximum(statuses.map((status) => status.lastSuccessfulPullAt))
5415
+ },
5416
+ ...maximum(statuses.map((status) => status.lastSuccessfulPushAt)) === undefined ? {} : {
5417
+ lastSuccessfulPushAt: maximum(statuses.map((status) => status.lastSuccessfulPushAt))
5418
+ },
5419
+ ...minimum(statuses.map((status) => status.oldestDeadLetterAt)) === undefined ? {} : {
5420
+ oldestDeadLetterAt: minimum(statuses.map((status) => status.oldestDeadLetterAt))
5421
+ },
5422
+ ...minimum(statuses.map((status) => status.oldestPendingAt)) === undefined ? {} : {
5423
+ oldestPendingAt: minimum(statuses.map((status) => status.oldestPendingAt))
5424
+ },
5425
+ pending: statuses.reduce((total, status) => total + status.pending, 0)
5426
+ };
5427
+ };
5428
+ var clientWithDeadLetter = async (operationId) => {
5429
+ for (const client of registry2.clients)
5430
+ if ((await client.listDeadLetters()).some((record) => record.operationId === operationId))
5431
+ return client;
5432
+ throw new Error(`Unknown Sync dead letter "${operationId}"`);
5433
+ };
5434
+ var retrySyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).retryDeadLetter(operationId);
5435
+ var discardSyncRuntimeDeadLetter = async (operationId) => (await clientWithDeadLetter(operationId)).discardDeadLetter(operationId);
5436
+ var rebaseSyncRuntimeDeadLetter = async (operationId, args) => (await clientWithDeadLetter(operationId)).rebaseDeadLetter(operationId, args);
5437
+
5438
+ // src/mobile/syncRemediation.ts
5439
+ var REMEDIATION_REGISTRY = Symbol.for("@absolutejs/mobile-sync-remediation");
5440
+ var LAST_INSTALLATION_OFFSET = -1;
5441
+ var isRegistry3 = (value) => typeof value === "object" && value !== null && Array.isArray(Reflect.get(value, "installations"));
5442
+ var resolveRegistry = () => {
5443
+ const existing = Reflect.get(globalThis, REMEDIATION_REGISTRY);
5444
+ if (isRegistry3(existing))
5445
+ return existing;
5446
+ const created = { installations: [] };
5447
+ Object.defineProperty(globalThis, REMEDIATION_REGISTRY, {
5448
+ configurable: false,
5449
+ enumerable: false,
5450
+ value: created,
5451
+ writable: false
5452
+ });
5453
+ return created;
5454
+ };
5455
+ var registry3 = resolveRegistry();
5456
+ var getAbsoluteMobileSyncRemediation = () => registry3.installations.at(LAST_INSTALLATION_OFFSET)?.bridge;
5457
+ var installAbsoluteMobileSyncRemediation = (bridge = {
5458
+ discard: discardSyncRuntimeDeadLetter,
5459
+ inspect: inspectSyncRuntime,
5460
+ rebase: rebaseSyncRuntimeDeadLetter,
5461
+ retry: retrySyncRuntimeDeadLetter
5462
+ }) => {
5463
+ const installation = { bridge };
5464
+ registry3.installations.push(installation);
5465
+ return () => {
5466
+ const index = registry3.installations.indexOf(installation);
5467
+ if (index >= 0)
5468
+ registry3.installations.splice(index, 1);
5469
+ };
5470
+ };
2798
5471
  // src/mobile/compatibilityDispatcher.ts
2799
5472
  import { Elysia as Elysia2 } from "elysia";
2800
5473
 
@@ -2816,6 +5489,64 @@ var ensureProducerStorage = () => {
2816
5489
  var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
2817
5490
 
2818
5491
  // src/mobile/compatibilityDispatcher.ts
5492
+ var MOBILE_WEBVIEW_ORIGINS = new Set([
5493
+ "capacitor://localhost",
5494
+ "http://localhost",
5495
+ "https://localhost"
5496
+ ]);
5497
+ var MOBILE_REQUEST_HEADER_NAMES = Object.values(MOBILE_PAGE_REQUEST_HEADERS);
5498
+ var MOBILE_CORS_ALLOW_HEADERS = [
5499
+ "accept",
5500
+ "content-type",
5501
+ "authorization",
5502
+ "hx-current-url",
5503
+ "hx-request",
5504
+ "hx-target",
5505
+ "hx-trigger",
5506
+ "hx-trigger-name",
5507
+ ...MOBILE_REQUEST_HEADER_NAMES
5508
+ ].join(", ");
5509
+ var MOBILE_CORS_METHODS = new Set([
5510
+ "DELETE",
5511
+ "GET",
5512
+ "HEAD",
5513
+ "OPTIONS",
5514
+ "PATCH",
5515
+ "POST",
5516
+ "PUT"
5517
+ ]);
5518
+ var mobileWebViewOrigin = (request) => {
5519
+ const origin = request.headers.get("origin");
5520
+ return origin && MOBILE_WEBVIEW_ORIGINS.has(origin) ? origin : undefined;
5521
+ };
5522
+ var applyMobileCorsHeaders = (response, origin) => {
5523
+ response.headers.set("access-control-allow-credentials", "true");
5524
+ response.headers.set("access-control-allow-origin", origin);
5525
+ response.headers.append("vary", "Origin");
5526
+ return response;
5527
+ };
5528
+ var mobilePreflightResponse = (request) => {
5529
+ if (request.method !== "OPTIONS")
5530
+ return;
5531
+ const origin = mobileWebViewOrigin(request);
5532
+ if (!origin)
5533
+ return;
5534
+ const requestedHeaders = request.headers.get("access-control-request-headers");
5535
+ const requestedMethod = request.headers.get("access-control-request-method")?.toUpperCase() ?? "";
5536
+ if (!MOBILE_CORS_METHODS.has(requestedMethod))
5537
+ return;
5538
+ return new Response(null, {
5539
+ headers: {
5540
+ "access-control-allow-credentials": "true",
5541
+ "access-control-allow-headers": requestedHeaders || MOBILE_CORS_ALLOW_HEADERS,
5542
+ "access-control-allow-methods": [...MOBILE_CORS_METHODS].join(", "),
5543
+ "access-control-allow-origin": origin,
5544
+ "access-control-max-age": "600",
5545
+ vary: "Origin, Access-Control-Request-Headers"
5546
+ },
5547
+ status: 204
5548
+ });
5549
+ };
2819
5550
  var artifactOwnsRequest = (artifact, pageId, request) => {
2820
5551
  const { pathname } = new URL(request.url);
2821
5552
  return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
@@ -2843,6 +5574,9 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2843
5574
  return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
2844
5575
  if (getCurrentAbsoluteMobileProducerContext())
2845
5576
  return;
5577
+ const preflight = mobilePreflightResponse(request);
5578
+ if (preflight)
5579
+ return preflight;
2846
5580
  const parsed = parseAbsoluteMobilePageRequest(request);
2847
5581
  if (parsed.kind !== "mobile")
2848
5582
  return;
@@ -2866,23 +5600,28 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2866
5600
  console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
2867
5601
  return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
2868
5602
  }
5603
+ }).afterHandle("global", ({ request, responseValue }) => {
5604
+ const origin = mobileWebViewOrigin(request);
5605
+ if (!origin || !(responseValue instanceof Response))
5606
+ return;
5607
+ applyMobileCorsHeaders(responseValue, origin);
2869
5608
  }).as("global");
2870
5609
  };
2871
5610
  // 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";
5611
+ import { readFile as readFile14, rename as rename11, writeFile as writeFile12 } from "fs/promises";
5612
+ import { join as join15 } from "path";
2874
5613
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
2875
5614
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
2876
5615
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
2877
5616
  var NOT_FOUND = -1;
2878
5617
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2879
5618
  var writeChangedFile = async (path, source) => {
2880
- const current = await readFile11(path, "utf8");
5619
+ const current = await readFile14(path, "utf8");
2881
5620
  if (current === source)
2882
5621
  return false;
2883
5622
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2884
- await writeFile9(temporary, source, { flag: "wx" });
2885
- await rename9(temporary, path);
5623
+ await writeFile12(temporary, source, { flag: "wx" });
5624
+ await rename11(temporary, path);
2886
5625
  return true;
2887
5626
  };
2888
5627
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -2906,7 +5645,7 @@ var replaceManagedRegion = (source, region, insertAt) => {
2906
5645
  return `${source.slice(0, index)}${region}${source.slice(index)}`;
2907
5646
  };
2908
5647
  var androidRegion = (config) => {
2909
- const hosts = config.deepLinkHosts.map((host) => ` <data android:scheme="https" android:host="${escapeXml(host)}" />`).join(`
5648
+ const hosts = config.deepLinkHosts.map((host3) => ` <data android:scheme="https" android:host="${escapeXml(host3)}" />`).join(`
2910
5649
  `);
2911
5650
  const customScheme = config.deepLinkScheme ? `
2912
5651
 
@@ -2927,8 +5666,8 @@ ${hosts}
2927
5666
  `;
2928
5667
  };
2929
5668
  var configureAndroid = async (config) => {
2930
- const path = join10(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
2931
- const source = await readFile11(path, "utf8");
5669
+ const path = join15(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
5670
+ const source = await readFile14(path, "utf8");
2932
5671
  const mainActivity = source.indexOf('android:name=".MainActivity"');
2933
5672
  if (mainActivity === NOT_FOUND) {
2934
5673
  throw new TypeError("Android MainActivity was not found.");
@@ -2953,8 +5692,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
2953
5692
  ${END_MARKER}
2954
5693
  `;
2955
5694
  var configureIosInfo = async (config) => {
2956
- const path = join10(config.nativeProjectDirectory, "ios/App/App/Info.plist");
2957
- const source = await readFile11(path, "utf8");
5695
+ const path = join15(config.nativeProjectDirectory, "ios/App/App/Info.plist");
5696
+ const source = await readFile14(path, "utf8");
2958
5697
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
2959
5698
  ${END_MARKER}
2960
5699
  `;
@@ -2962,7 +5701,7 @@ var configureIosInfo = async (config) => {
2962
5701
  return writeChangedFile(path, updated);
2963
5702
  };
2964
5703
  var iosEntitlementsSource = (config) => {
2965
- const domains = config.deepLinkHosts.map((host) => ` <string>applinks:${escapeXml(host)}</string>`).join(`
5704
+ const domains = config.deepLinkHosts.map((host3) => ` <string>applinks:${escapeXml(host3)}</string>`).join(`
2966
5705
  `);
2967
5706
  return `<?xml version="1.0" encoding="UTF-8"?>
2968
5707
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -2977,10 +5716,10 @@ ${domains}
2977
5716
  `;
2978
5717
  };
2979
5718
  var configureIosEntitlements = async (config) => {
2980
- const path = join10(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
5719
+ const path = join15(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
2981
5720
  let current = "";
2982
5721
  try {
2983
- current = await readFile11(path, "utf8");
5722
+ current = await readFile14(path, "utf8");
2984
5723
  } catch (error) {
2985
5724
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2986
5725
  throw error;
@@ -2990,13 +5729,13 @@ var configureIosEntitlements = async (config) => {
2990
5729
  if (current === source)
2991
5730
  return false;
2992
5731
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2993
- await writeFile9(temporary, source, { flag: "wx" });
2994
- await rename9(temporary, path);
5732
+ await writeFile12(temporary, source, { flag: "wx" });
5733
+ await rename11(temporary, path);
2995
5734
  return true;
2996
5735
  };
2997
5736
  var configureIosProject = async (config) => {
2998
- const path = join10(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
2999
- const source = await readFile11(path, "utf8");
5737
+ const path = join15(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
5738
+ const source = await readFile14(path, "utf8");
3000
5739
  const declarations = [
3001
5740
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
3002
5741
  ].map((match) => match[1]);
@@ -3033,9 +5772,93 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
3033
5772
  changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
3034
5773
  };
3035
5774
  };
5775
+ // src/mobile/nativeDeviceCapabilities.ts
5776
+ import { readFile as readFile15, rename as rename12, writeFile as writeFile13 } from "fs/promises";
5777
+ import { join as join16 } from "path";
5778
+ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
5779
+ var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
5780
+ var NOT_FOUND2 = -1;
5781
+ var escapeXml2 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
5782
+ var writeChangedFile2 = async (path, source) => {
5783
+ const current = await readFile15(path, "utf8");
5784
+ if (current === source)
5785
+ return false;
5786
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
5787
+ await writeFile13(temporary, source, { flag: "wx" });
5788
+ await rename12(temporary, path);
5789
+ return true;
5790
+ };
5791
+ var managed = (source, region, insertion) => {
5792
+ const start = source.indexOf(START_MARKER2);
5793
+ const end = source.indexOf(END_MARKER2);
5794
+ if (start === NOT_FOUND2 !== (end === NOT_FOUND2) || start !== NOT_FOUND2 && end < start)
5795
+ throw new TypeError("AbsoluteJS device-capability ownership markers are malformed.");
5796
+ if (start !== NOT_FOUND2) {
5797
+ const lineStart = source.lastIndexOf(`
5798
+ `, start) + 1;
5799
+ const nextLine = source.indexOf(`
5800
+ `, end + END_MARKER2.length);
5801
+ const lineEnd = nextLine === NOT_FOUND2 ? source.length : nextLine + 1;
5802
+ return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
5803
+ }
5804
+ if (region.length === 0)
5805
+ return source;
5806
+ if (insertion === NOT_FOUND2)
5807
+ throw new TypeError("Could not find a safe native project location for device permissions.");
5808
+ return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
5809
+ };
5810
+ var IOS_KEYS = {
5811
+ camera: "NSCameraUsageDescription",
5812
+ "photo-library": "NSPhotoLibraryUsageDescription",
5813
+ "photo-library-add": "NSPhotoLibraryAddUsageDescription"
5814
+ };
5815
+ var iosDescription = (appName, purpose) => {
5816
+ if (purpose === "camera")
5817
+ return `${appName} uses your camera when you choose to take a photo.`;
5818
+ if (purpose === "photo-library")
5819
+ return `${appName} accesses your photo library only for photo actions you choose.`;
5820
+ return `${appName} adds to your photo library only for photo actions you choose.`;
5821
+ };
5822
+ var configureIos2 = async (config, plan) => {
5823
+ const path = join16(config.nativeProjectDirectory, "ios/App/App/Info.plist");
5824
+ const source = await readFile15(path, "utf8");
5825
+ const requirements = absoluteDeviceNativeRequirements(plan);
5826
+ const content = requirements.iosUsageDescriptions.map((purpose) => ` <key>${IOS_KEYS[purpose]}</key>
5827
+ <string>${escapeXml2(iosDescription(config.appName, purpose))}</string>`).join(`
5828
+ `);
5829
+ const region = content ? ` ${START_MARKER2}
5830
+ ${content}
5831
+ ${END_MARKER2}
5832
+ ` : "";
5833
+ return writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
5834
+ };
5835
+ var configureAndroid2 = async (config, plan) => {
5836
+ const path = join16(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
5837
+ const source = await readFile15(path, "utf8");
5838
+ const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
5839
+ const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
5840
+ `);
5841
+ const region = content ? ` ${START_MARKER2}
5842
+ ${content}
5843
+ ${END_MARKER2}
5844
+ ` : "";
5845
+ const application = source.indexOf("<application");
5846
+ const insertion = application === NOT_FOUND2 ? NOT_FOUND2 : source.lastIndexOf(`
5847
+ `, application) + 1;
5848
+ return writeChangedFile2(path, managed(source, region, insertion));
5849
+ };
5850
+ var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platforms = config.platforms, plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot)) => {
5851
+ const results = await Promise.all(platforms.map(async (platform) => ({
5852
+ didChange: platform === "ios" ? await configureIos2(config, plan) : await configureAndroid2(config, plan),
5853
+ platform
5854
+ })));
5855
+ return {
5856
+ changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
5857
+ };
5858
+ };
3036
5859
  // 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";
5860
+ import { access as access9 } from "fs/promises";
5861
+ import { isAbsolute as isAbsolute6, relative as relative10, resolve as resolve13, sep as sep6 } from "path";
3039
5862
  import { pathToFileURL as pathToFileURL3 } from "url";
3040
5863
  var prepareAbsoluteIosRelease = async (publisher, options) => {
3041
5864
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -3058,24 +5881,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
3058
5881
  }
3059
5882
  return versionCode;
3060
5883
  };
3061
- var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3062
- var isPublisher = (value) => isRecord7(value) && typeof value.publish === "function";
5884
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5885
+ var isPublisher = (value) => isRecord8(value) && typeof value.publish === "function";
3063
5886
  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)) {
5887
+ const root = resolve13(projectRoot);
5888
+ const path = resolve13(root, requested);
5889
+ const projectRelative = relative10(root, path);
5890
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
3068
5891
  throw new TypeError("mobile publish --registry must remain inside the project.");
3069
5892
  }
3070
5893
  return path;
3071
5894
  };
3072
5895
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
3073
5896
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
3074
- await access7(modulePath).catch(() => {
5897
+ await access9(modulePath).catch(() => {
3075
5898
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
3076
5899
  });
3077
5900
  const loaded = await import(pathToFileURL3(modulePath).href);
3078
- const publisher = isRecord7(loaded) ? loaded.default ?? loaded.registry : undefined;
5901
+ const publisher = isRecord8(loaded) ? loaded.default ?? loaded.registry : undefined;
3079
5902
  if (!isPublisher(publisher)) {
3080
5903
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
3081
5904
  }
@@ -3132,67 +5955,112 @@ var publishAbsoluteIosRelease = async (options) => {
3132
5955
  return publication;
3133
5956
  };
3134
5957
  // 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";
5958
+ import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
5959
+ import { dirname as dirname10, extname as extname4, relative as relative11, resolve as resolve14 } from "path";
5960
+ import ts2 from "typescript";
3138
5961
  var ROUTE_METHODS = new Set(["get", "head"]);
3139
5962
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
3140
- var PAGE_HANDLER = "handleReactPageRequest";
5963
+ var PAGE_HANDLERS = new Map([
5964
+ [
5965
+ "handleHTMLPageRequest",
5966
+ { framework: "html", inputKind: "static", propsProperty: "props" }
5967
+ ],
5968
+ [
5969
+ "handleHTMXPageRequest",
5970
+ { framework: "htmx", inputKind: "static", propsProperty: "props" }
5971
+ ],
5972
+ [
5973
+ "handleAngularPageRequest",
5974
+ {
5975
+ bundleProperty: "indexPath",
5976
+ framework: "angular",
5977
+ propsProperty: "requestContext",
5978
+ sourceProperty: "pagePath"
5979
+ }
5980
+ ],
5981
+ [
5982
+ "handleReactPageRequest",
5983
+ {
5984
+ bundleProperty: "index",
5985
+ framework: "react",
5986
+ pageProperty: "Page",
5987
+ propsProperty: "props"
5988
+ }
5989
+ ],
5990
+ [
5991
+ "handleSveltePageRequest",
5992
+ {
5993
+ bundleProperty: "indexPath",
5994
+ framework: "svelte",
5995
+ propsProperty: "props",
5996
+ sourceProperty: "pagePath"
5997
+ }
5998
+ ],
5999
+ [
6000
+ "handleVuePageRequest",
6001
+ {
6002
+ bundleProperty: "indexPath",
6003
+ framework: "vue",
6004
+ propsProperty: "props",
6005
+ sourceProperty: "pagePath"
6006
+ }
6007
+ ]
6008
+ ]);
3141
6009
  var posixPath = (value) => value.replace(/\\/g, "/");
3142
- var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname6(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
6010
+ var findTsconfig = (entry, projectRoot) => ts2.findConfigFile(dirname10(entry), existsSync3, "tsconfig.json") ?? ts2.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
3143
6011
  var createProgram = (entry, projectRoot) => {
3144
6012
  const configPath = findTsconfig(entry, projectRoot);
3145
6013
  if (!configPath) {
3146
- return ts.createProgram([entry], {
6014
+ return ts2.createProgram([entry], {
3147
6015
  allowJs: true,
3148
- jsx: ts.JsxEmit.ReactJSX,
3149
- module: ts.ModuleKind.ESNext,
3150
- moduleResolution: ts.ModuleResolutionKind.Bundler,
3151
- target: ts.ScriptTarget.ESNext
6016
+ jsx: ts2.JsxEmit.ReactJSX,
6017
+ module: ts2.ModuleKind.ESNext,
6018
+ moduleResolution: ts2.ModuleResolutionKind.Bundler,
6019
+ target: ts2.ScriptTarget.ESNext
3152
6020
  });
3153
6021
  }
3154
- const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname6(configPath));
6022
+ const parsed = ts2.parseJsonConfigFileContent(ts2.readConfigFile(configPath, (path) => readFileSync5(path, "utf8")).config, ts2.sys, dirname10(configPath));
3155
6023
  if (!parsed.fileNames.includes(entry))
3156
6024
  parsed.fileNames.push(entry);
3157
- return ts.createProgram(parsed.fileNames, parsed.options);
6025
+ return ts2.createProgram(parsed.fileNames, parsed.options);
3158
6026
  };
3159
6027
  var propertyName = (property) => {
3160
6028
  if (!("name" in property) || !property.name)
3161
6029
  return;
3162
- if (ts.isIdentifier(property.name))
6030
+ if (ts2.isIdentifier(property.name))
3163
6031
  return property.name.text;
3164
- if (ts.isStringLiteralLike(property.name))
6032
+ if (ts2.isStringLiteralLike(property.name))
3165
6033
  return property.name.text;
3166
6034
  return;
3167
6035
  };
3168
- var objectPropertyExpression = (object, name) => {
3169
- const property = object.properties.find((candidate) => propertyName(candidate) === name);
3170
- if (property && ts.isPropertyAssignment(property)) {
6036
+ var objectPropertyExpression = (object3, name) => {
6037
+ const property = object3.properties.find((candidate) => propertyName(candidate) === name);
6038
+ if (property && ts2.isPropertyAssignment(property)) {
3171
6039
  return property.initializer;
3172
6040
  }
3173
- if (property && ts.isShorthandPropertyAssignment(property)) {
6041
+ if (property && ts2.isShorthandPropertyAssignment(property)) {
3174
6042
  return property.name;
3175
6043
  }
3176
6044
  return;
3177
6045
  };
3178
6046
  var serializeType = (type, checker, ancestors = new Set) => {
3179
- if (type.flags & ts.TypeFlags.Any)
6047
+ if (type.flags & ts2.TypeFlags.Any)
3180
6048
  return { type: "any" };
3181
- if (type.flags & ts.TypeFlags.Unknown)
6049
+ if (type.flags & ts2.TypeFlags.Unknown)
3182
6050
  return { type: "unknown" };
3183
- if (type.flags & ts.TypeFlags.Never)
6051
+ if (type.flags & ts2.TypeFlags.Never)
3184
6052
  return { type: "never" };
3185
- if (type.flags & ts.TypeFlags.StringLike)
6053
+ if (type.flags & ts2.TypeFlags.StringLike)
3186
6054
  return { type: "string" };
3187
- if (type.flags & ts.TypeFlags.NumberLike)
6055
+ if (type.flags & ts2.TypeFlags.NumberLike)
3188
6056
  return { type: "number" };
3189
- if (type.flags & ts.TypeFlags.BooleanLike)
6057
+ if (type.flags & ts2.TypeFlags.BooleanLike)
3190
6058
  return { type: "boolean" };
3191
- if (type.flags & ts.TypeFlags.BigIntLike)
6059
+ if (type.flags & ts2.TypeFlags.BigIntLike)
3192
6060
  return { type: "bigint" };
3193
- if (type.flags & ts.TypeFlags.Null)
6061
+ if (type.flags & ts2.TypeFlags.Null)
3194
6062
  return { type: "null" };
3195
- if (type.flags & ts.TypeFlags.Undefined)
6063
+ if (type.flags & ts2.TypeFlags.Undefined)
3196
6064
  return { type: "undefined" };
3197
6065
  if (type.isUnion()) {
3198
6066
  return {
@@ -3206,11 +6074,11 @@ var serializeType = (type, checker, ancestors = new Set) => {
3206
6074
  }
3207
6075
  if (ancestors.has(type)) {
3208
6076
  return {
3209
- ref: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
6077
+ ref: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
3210
6078
  };
3211
6079
  }
3212
6080
  ancestors.add(type);
3213
- const arrayElement = checker.getIndexTypeOfType(type, ts.IndexKind.Number);
6081
+ const arrayElement = checker.getIndexTypeOfType(type, ts2.IndexKind.Number);
3214
6082
  const properties = checker.getPropertiesOfType(type);
3215
6083
  let schema;
3216
6084
  if (arrayElement && properties.some(({ name }) => name === "length")) {
@@ -3225,7 +6093,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
3225
6093
  return [
3226
6094
  property.name,
3227
6095
  {
3228
- optional: Boolean(property.flags & ts.SymbolFlags.Optional),
6096
+ optional: Boolean(property.flags & ts2.SymbolFlags.Optional),
3229
6097
  schema: serializeType(propertyType, checker, ancestors)
3230
6098
  }
3231
6099
  ];
@@ -3233,7 +6101,7 @@ var serializeType = (type, checker, ancestors = new Set) => {
3233
6101
  schema = { properties: Object.fromEntries(entries), type: "object" };
3234
6102
  } else {
3235
6103
  schema = {
3236
- type: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
6104
+ type: checker.typeToString(type, undefined, ts2.TypeFormatFlags.NoTruncation)
3237
6105
  };
3238
6106
  }
3239
6107
  ancestors.delete(type);
@@ -3251,25 +6119,25 @@ var pagePropsType = (pageExpression, propsExpression, checker) => {
3251
6119
  };
3252
6120
  var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
3253
6121
  let symbol = checker.getSymbolAtLocation(expression);
3254
- if (symbol?.flags && symbol.flags & ts.SymbolFlags.Alias) {
6122
+ if (symbol?.flags && symbol.flags & ts2.SymbolFlags.Alias) {
3255
6123
  symbol = checker.getAliasedSymbol(symbol);
3256
6124
  }
3257
6125
  const declaration = symbol?.declarations?.[0];
3258
6126
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
3259
6127
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
3260
- const source = posixPath(relative7(projectRoot, file));
6128
+ const source = posixPath(relative11(projectRoot, file));
3261
6129
  return `${source}#${exportedName}`;
3262
6130
  };
3263
6131
  var resolveAlias = (symbol, checker) => {
3264
- if (!(symbol.flags & ts.SymbolFlags.Alias))
6132
+ if (!(symbol.flags & ts2.SymbolFlags.Alias))
3265
6133
  return symbol;
3266
6134
  return checker.getAliasedSymbol(symbol);
3267
6135
  };
3268
6136
  var assetKey = (expression, checker, seen = new Set) => {
3269
6137
  if (!expression)
3270
6138
  return;
3271
- if (ts.isIdentifier(expression)) {
3272
- const unresolved = ts.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
6139
+ if (ts2.isIdentifier(expression)) {
6140
+ const unresolved = ts2.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
3273
6141
  if (!unresolved)
3274
6142
  return;
3275
6143
  const symbol = resolveAlias(unresolved, checker);
@@ -3277,28 +6145,123 @@ var assetKey = (expression, checker, seen = new Set) => {
3277
6145
  return;
3278
6146
  seen.add(symbol);
3279
6147
  const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
3280
- if (!declaration || !ts.isVariableDeclaration(declaration))
6148
+ if (!declaration || !ts2.isVariableDeclaration(declaration))
3281
6149
  return;
3282
6150
  return assetKey(declaration.initializer, checker, seen);
3283
6151
  }
3284
- if (!ts.isCallExpression(expression))
6152
+ if (!ts2.isCallExpression(expression))
3285
6153
  return;
3286
- if (!ts.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
6154
+ if (!ts2.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
3287
6155
  return;
3288
6156
  }
3289
6157
  const [, key] = expression.arguments;
3290
- return key && ts.isStringLiteralLike(key) ? key.text : undefined;
6158
+ return key && ts2.isStringLiteralLike(key) ? key.text : undefined;
6159
+ };
6160
+ var staticString = (expression, bindings) => {
6161
+ if (ts2.isStringLiteralLike(expression))
6162
+ return expression.text;
6163
+ if (ts2.isIdentifier(expression))
6164
+ return bindings.get(expression.text);
6165
+ if (ts2.isNoSubstitutionTemplateLiteral(expression))
6166
+ return expression.text;
6167
+ if (!ts2.isTemplateExpression(expression))
6168
+ return;
6169
+ let value = expression.head.text;
6170
+ for (const span of expression.templateSpans) {
6171
+ const substitution = staticString(span.expression, bindings);
6172
+ if (substitution === undefined)
6173
+ return;
6174
+ value += substitution + span.literal.text;
6175
+ }
6176
+ return value;
6177
+ };
6178
+ var assetKeyWithBindings = (expression, checker, bindings = new Map) => {
6179
+ if (!expression)
6180
+ return;
6181
+ if (ts2.isCallExpression(expression) && ts2.isIdentifier(expression.expression) && expression.expression.text === "asset") {
6182
+ const [, key] = expression.arguments;
6183
+ return key ? staticString(key, bindings) : undefined;
6184
+ }
6185
+ return assetKey(expression, checker);
6186
+ };
6187
+ var callableObject = (call, checker) => {
6188
+ const symbol = checker.getSymbolAtLocation(call.expression);
6189
+ const resolved = symbol ? resolveAlias(symbol, checker) : undefined;
6190
+ const declaration = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
6191
+ let callable;
6192
+ if (declaration && ts2.isFunctionDeclaration(declaration)) {
6193
+ callable = declaration;
6194
+ } else if (declaration && ts2.isVariableDeclaration(declaration) && declaration.initializer && (ts2.isArrowFunction(declaration.initializer) || ts2.isFunctionExpression(declaration.initializer))) {
6195
+ callable = declaration.initializer;
6196
+ }
6197
+ if (!callable)
6198
+ return;
6199
+ const bindings = new Map;
6200
+ callable.parameters.forEach((parameter, index) => {
6201
+ if (!ts2.isIdentifier(parameter.name))
6202
+ return;
6203
+ const argument = call.arguments[index];
6204
+ if (!argument)
6205
+ return;
6206
+ const value = staticString(argument, new Map);
6207
+ if (value !== undefined)
6208
+ bindings.set(parameter.name.text, value);
6209
+ });
6210
+ const { body } = callable;
6211
+ if (!body)
6212
+ return;
6213
+ const expressionBody = ts2.isParenthesizedExpression(body) ? body.expression : body;
6214
+ if (ts2.isObjectLiteralExpression(expressionBody)) {
6215
+ return { bindings, object: expressionBody };
6216
+ }
6217
+ if (ts2.isBlock(body)) {
6218
+ const returned = body.statements.find(ts2.isReturnStatement)?.expression;
6219
+ if (returned && ts2.isObjectLiteralExpression(returned)) {
6220
+ return { bindings, object: returned };
6221
+ }
6222
+ }
6223
+ return;
6224
+ };
6225
+ var spreadObject = (expression, checker, bindings) => {
6226
+ if (ts2.isObjectLiteralExpression(expression)) {
6227
+ return { bindings, object: expression };
6228
+ }
6229
+ if (!ts2.isCallExpression(expression))
6230
+ return;
6231
+ return callableObject(expression, checker);
6232
+ };
6233
+ var objectAssetKey = (object3, name, checker, bindings = new Map) => {
6234
+ for (const property of [...object3.properties].reverse()) {
6235
+ if (propertyName(property) === name && ts2.isShorthandPropertyAssignment(property)) {
6236
+ return assetKeyWithBindings(property.name, checker, bindings);
6237
+ }
6238
+ if (propertyName(property) === name && ts2.isPropertyAssignment(property)) {
6239
+ return assetKeyWithBindings(property.initializer, checker, bindings);
6240
+ }
6241
+ if (!ts2.isSpreadAssignment(property))
6242
+ continue;
6243
+ const nestedObject = spreadObject(property.expression, checker, bindings);
6244
+ if (!nestedObject)
6245
+ continue;
6246
+ const nested = objectAssetKey(nestedObject.object, name, checker, nestedObject.bindings);
6247
+ if (nested)
6248
+ return nested;
6249
+ }
6250
+ return;
3291
6251
  };
3292
6252
  var findPageCall = (nodes) => {
3293
6253
  let found;
3294
6254
  const visit = (candidate) => {
3295
6255
  if (found)
3296
6256
  return;
3297
- if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && candidate.expression.text === PAGE_HANDLER) {
3298
- found = candidate;
6257
+ if (ts2.isCallExpression(candidate) && ts2.isIdentifier(candidate.expression) && PAGE_HANDLERS.has(candidate.expression.text)) {
6258
+ const definition = PAGE_HANDLERS.get(candidate.expression.text);
6259
+ if (!definition)
6260
+ return;
6261
+ found = { definition, node: candidate };
3299
6262
  return;
3300
6263
  }
3301
- ts.forEachChild(candidate, visit);
6264
+ ts2.forEachChild(candidate, visit);
3302
6265
  };
3303
6266
  for (const node of nodes)
3304
6267
  visit(node);
@@ -3307,37 +6270,74 @@ var findPageCall = (nodes) => {
3307
6270
  var isProjectSource = (sourceFile, resolvedFile, projectRoot) => !sourceFile.isDeclarationFile && !resolvedFile.includes("/node_modules/") && resolvedFile.startsWith(`${projectRoot}/`);
3308
6271
  var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
3309
6272
  const callee = node.expression;
3310
- if (!ts.isPropertyAccessExpression(callee))
6273
+ if (!ts2.isPropertyAccessExpression(callee))
3311
6274
  return;
3312
6275
  if (!ROUTE_METHODS.has(callee.name.text))
3313
6276
  return;
3314
6277
  const [routePath] = node.arguments;
3315
- if (!routePath || !ts.isStringLiteralLike(routePath))
6278
+ if (!routePath || !ts2.isStringLiteralLike(routePath))
3316
6279
  return;
3317
- const pageCall = findPageCall(node.arguments.slice(1));
6280
+ const foundPageCall = findPageCall(node.arguments.slice(1));
6281
+ const pageCall = foundPageCall?.node;
6282
+ const definition = foundPageCall?.definition;
3318
6283
  const [input] = pageCall?.arguments ?? [];
3319
- if (!pageCall || !input || !ts.isObjectLiteralExpression(input)) {
6284
+ if (!pageCall || !input) {
3320
6285
  return;
3321
6286
  }
3322
- const page = objectPropertyExpression(input, "Page");
3323
- if (!page)
6287
+ if (!definition)
6288
+ return;
6289
+ if (definition.inputKind === "static") {
6290
+ const bundleKey2 = assetKey(input, checker);
6291
+ if (!bundleKey2)
6292
+ return;
6293
+ const pageId2 = `${definition.framework}:${bundleKey2}`;
6294
+ const propsSchemaHash2 = hashAbsoluteMobilePropsSchema({
6295
+ properties: {},
6296
+ type: "object"
6297
+ });
6298
+ return {
6299
+ inputKind: "static",
6300
+ metadata: {
6301
+ bundleKey: bundleKey2,
6302
+ contract: `${definition.framework}:${pageId2}:${propsSchemaHash2}`,
6303
+ framework: definition.framework,
6304
+ pageId: pageId2,
6305
+ propsSchemaHash: propsSchemaHash2
6306
+ },
6307
+ pageCallStart: pageCall.getStart(sourceFile),
6308
+ routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
6309
+ };
6310
+ }
6311
+ if (!ts2.isObjectLiteralExpression(input) || !definition.bundleProperty) {
6312
+ return;
6313
+ }
6314
+ const page = definition.pageProperty ? objectPropertyExpression(input, definition.pageProperty) : undefined;
6315
+ const source = definition.sourceProperty ? objectAssetKey(input, definition.sourceProperty, checker) : undefined;
6316
+ if (definition.pageProperty && !page)
6317
+ return;
6318
+ if (definition.sourceProperty && !source)
3324
6319
  return;
3325
- const props = objectPropertyExpression(input, "props");
3326
- const index = objectPropertyExpression(input, "index");
3327
- const bundleKey = assetKey(index, checker);
6320
+ const props = objectPropertyExpression(input, definition.propsProperty);
6321
+ const bundleKey = objectAssetKey(input, definition.bundleProperty, checker);
3328
6322
  if (!bundleKey)
3329
6323
  return;
3330
- const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot);
3331
- const schema = serializeType(pagePropsType(page, props, checker), checker);
6324
+ const pageId = page ? resolvePageIdentity(page, sourceFile, checker, projectRoot) : `${definition.framework}:${source}`;
6325
+ let propsType;
6326
+ if (page)
6327
+ propsType = pagePropsType(page, props, checker);
6328
+ else if (props)
6329
+ propsType = checker.getTypeAtLocation(props);
6330
+ const schema = propsType ? serializeType(propsType, checker) : { properties: {}, type: "object" };
3332
6331
  const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
3333
6332
  const metadata = {
3334
6333
  bundleKey,
3335
- contract: `react:${pageId}:${propsSchemaHash}`,
3336
- framework: "react",
6334
+ contract: `${definition.framework}:${pageId}:${propsSchemaHash}`,
6335
+ framework: definition.framework,
3337
6336
  pageId,
3338
6337
  propsSchemaHash
3339
6338
  };
3340
6339
  const result = {
6340
+ inputKind: "object",
3341
6341
  metadata,
3342
6342
  pageCallStart: pageCall.getStart(sourceFile),
3343
6343
  routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
@@ -3350,21 +6350,21 @@ var analyzeSourceFile = (sourceFile, checker, projectRoot) => {
3350
6350
  byRouteCall: new Map
3351
6351
  };
3352
6352
  const visit = (node) => {
3353
- const result = ts.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
6353
+ const result = ts2.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
3354
6354
  if (result) {
3355
6355
  analysis.byPageCall.set(result.pageCallStart, result);
3356
6356
  analysis.byRouteCall.set(result.routeCallSpan, result);
3357
6357
  }
3358
- ts.forEachChild(node, visit);
6358
+ ts2.forEachChild(node, visit);
3359
6359
  };
3360
- ts.forEachChild(sourceFile, visit);
6360
+ ts2.forEachChild(sourceFile, visit);
3361
6361
  return analysis;
3362
6362
  };
3363
6363
  var analyzeProgram = (program, projectRoot) => {
3364
6364
  const checker = program.getTypeChecker();
3365
6365
  const analyzed = new Map;
3366
6366
  for (const sourceFile of program.getSourceFiles()) {
3367
- const resolvedFile = resolve11(sourceFile.fileName);
6367
+ const resolvedFile = resolve14(sourceFile.fileName);
3368
6368
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
3369
6369
  continue;
3370
6370
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -3373,34 +6373,44 @@ var analyzeProgram = (program, projectRoot) => {
3373
6373
  }
3374
6374
  return analyzed;
3375
6375
  };
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);
6376
+ 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
6377
  var routeOptions = (existing, metadata) => {
3378
- const detail = ts.factory.createObjectLiteralExpression([
3379
- ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
6378
+ const detail = ts2.factory.createObjectLiteralExpression([
6379
+ ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
3380
6380
  ]);
3381
6381
  if (!existing) {
3382
- return ts.factory.createObjectLiteralExpression([
3383
- ts.factory.createPropertyAssignment("detail", detail)
6382
+ return ts2.factory.createObjectLiteralExpression([
6383
+ ts2.factory.createPropertyAssignment("detail", detail)
3384
6384
  ]);
3385
6385
  }
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))
6386
+ return ts2.factory.createObjectLiteralExpression([
6387
+ ts2.factory.createSpreadAssignment(existing),
6388
+ ts2.factory.createPropertyAssignment("detail", ts2.factory.createObjectLiteralExpression([
6389
+ ts2.factory.createSpreadAssignment(ts2.factory.createPropertyAccessExpression(existing, "detail")),
6390
+ ts2.factory.createPropertyAssignment(ts2.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
3391
6391
  ]))
3392
6392
  ]);
3393
6393
  };
3394
6394
  var transformPageCall = (node, page) => {
3395
6395
  if (!page)
3396
6396
  return;
6397
+ if (page.inputKind === "static") {
6398
+ const [pagePath, existingOptions, ...rest] = node.arguments;
6399
+ if (!pagePath)
6400
+ return;
6401
+ const options = ts2.factory.createObjectLiteralExpression([
6402
+ ...existingOptions ? [ts2.factory.createSpreadAssignment(existingOptions)] : [],
6403
+ ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
6404
+ ]);
6405
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [pagePath, options, ...rest]);
6406
+ }
3397
6407
  const [input] = node.arguments;
3398
- if (!input || !ts.isObjectLiteralExpression(input))
6408
+ if (!input || !ts2.isObjectLiteralExpression(input))
3399
6409
  return;
3400
- return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
3401
- ts.factory.updateObjectLiteralExpression(input, [
6410
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [
6411
+ ts2.factory.updateObjectLiteralExpression(input, [
3402
6412
  ...input.properties,
3403
- ts.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
6413
+ ts2.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
3404
6414
  ]),
3405
6415
  ...node.arguments.slice(1)
3406
6416
  ]);
@@ -3413,16 +6423,16 @@ var transformRouteCall = (node, route) => {
3413
6423
  return;
3414
6424
  const options = maybeHandler ? maybeOptions : undefined;
3415
6425
  const handler = maybeHandler ?? maybeOptions;
3416
- return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
6426
+ return ts2.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
3417
6427
  };
3418
6428
  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);
6429
+ const sourceFile = ts2.createSourceFile(fileName, source, ts2.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS);
3420
6430
  const transformer = (context) => {
3421
6431
  const visit = (node) => {
3422
- if (!ts.isCallExpression(node)) {
3423
- return ts.visitEachChild(node, visit, context);
6432
+ if (!ts2.isCallExpression(node)) {
6433
+ return ts2.visitEachChild(node, visit, context);
3424
6434
  }
3425
- const transformedChildren = ts.visitEachChild(node, visit, context);
6435
+ const transformedChildren = ts2.visitEachChild(node, visit, context);
3426
6436
  const page = analysis.byPageCall.get(node.getStart(sourceFile));
3427
6437
  const transformedPage = transformPageCall(transformedChildren, page);
3428
6438
  if (transformedPage)
@@ -3433,81 +6443,120 @@ var transformFile = (source, fileName, analysis) => {
3433
6443
  return transformedRoute;
3434
6444
  return transformedChildren;
3435
6445
  };
3436
- return (node) => ts.visitNode(node, visit, ts.isSourceFile) ?? node;
6446
+ return (node) => ts2.visitNode(node, visit, ts2.isSourceFile) ?? node;
3437
6447
  };
3438
- const result = ts.transform(sourceFile, [transformer]);
6448
+ const result = ts2.transform(sourceFile, [transformer]);
3439
6449
  try {
3440
6450
  const [transformed] = result.transformed;
3441
6451
  if (!transformed)
3442
6452
  throw new TypeError("Mobile route transform failed.");
3443
- return ts.createPrinter().printFile(transformed);
6453
+ return ts2.createPrinter().printFile(transformed);
3444
6454
  } finally {
3445
6455
  result.dispose();
3446
6456
  }
3447
6457
  };
3448
6458
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
3449
6459
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
3450
- const projectRoot = resolve11(options.projectRoot ?? process.cwd());
3451
- const entry = resolve11(options.entry);
6460
+ const projectRoot = resolve14(options.projectRoot ?? process.cwd());
6461
+ const entry = resolve14(options.entry);
3452
6462
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
3453
6463
  return {
3454
6464
  name: "absolute-mobile-route-metadata",
3455
6465
  setup(build) {
3456
6466
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
3457
- const analysis = analyzed.get(resolve11(path));
6467
+ const analysis = analyzed.get(resolve14(path));
3458
6468
  if (!analysis)
3459
6469
  return;
3460
6470
  const source = await Bun.file(path).text();
3461
6471
  return {
3462
6472
  contents: transformFile(source, path, analysis),
3463
- loader: extname2(path).endsWith("x") ? "tsx" : "ts"
6473
+ loader: extname4(path).endsWith("x") ? "tsx" : "ts"
3464
6474
  };
3465
6475
  });
3466
6476
  }
3467
6477
  };
3468
6478
  };
3469
6479
  var inspectAbsoluteMobileRouteMetadata = (options) => {
3470
- const projectRoot = resolve11(options.projectRoot ?? process.cwd());
3471
- const entry = resolve11(options.entry);
6480
+ const projectRoot = resolve14(options.projectRoot ?? process.cwd());
6481
+ const entry = resolve14(options.entry);
3472
6482
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
3473
6483
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
3474
- file: posixPath(relative7(projectRoot, file)),
6484
+ file: posixPath(relative11(projectRoot, file)),
3475
6485
  metadata
3476
6486
  })));
3477
6487
  };
3478
6488
  export {
3479
6489
  writeAbsoluteCapacitorConfig,
6490
+ waitForAbsoluteIosHmrLog,
3480
6491
  verifyAbsoluteMobileCompatibilityProducer,
3481
6492
  verifyAbsoluteMobileAssociationFiles,
6493
+ validateAbsoluteSshDestination,
6494
+ validateAbsoluteRemoteMacProfileName,
6495
+ syncAbsoluteRemoteMacProject,
6496
+ startAbsoluteRemoteIosDevSession,
6497
+ startAbsoluteIosDevSession,
6498
+ serializeAbsoluteMobileAuthEnvironment,
3482
6499
  runWithAbsoluteMobileProducer,
3483
6500
  retainAbsoluteMobileCompatibilityArtifacts,
3484
6501
  resolveAbsoluteMobileRoute,
6502
+ resolveAbsoluteMobileNavigation,
3485
6503
  resolveAbsoluteMobileDeepLink,
3486
6504
  resolveAbsoluteMobileCompatibilityRelease,
6505
+ resolveAbsoluteMobileAuthManifest,
6506
+ resolveAbsoluteDeviceCapabilityPlan,
6507
+ repairAbsoluteIosDevSession,
6508
+ removeAbsoluteRemoteMacProfile,
6509
+ redactAbsoluteIosLog,
3487
6510
  readAbsoluteMobileMaterializedReleases,
3488
6511
  publishAbsoluteIosRelease,
3489
6512
  publishAbsoluteAndroidRelease,
6513
+ projectUsesAbsoluteSync,
6514
+ projectUsesAbsoluteAuth,
3490
6515
  prepareAbsoluteIosRelease,
6516
+ prepareAbsoluteIosDevProject,
3491
6517
  prepareAbsoluteAndroidRelease,
6518
+ parseIosSimulators,
6519
+ parseIosRuntimes,
6520
+ parseIosDeviceTypes,
3492
6521
  parseAbsoluteMobilePageRequest,
3493
6522
  parseAbsoluteMobilePageEnvelope,
3494
6523
  parseAbsoluteMobileCompatibilityArtifact,
3495
6524
  parseAbsoluteMobileBuildPageMetadata,
6525
+ parseAbsoluteIosLogLine,
6526
+ parseAbsoluteIosHmrLog,
6527
+ pairAbsoluteRemoteMac,
3496
6528
  normalizeAbsoluteMobileConfig,
3497
6529
  navigateAbsoluteMobilePage,
6530
+ missingAbsoluteDeviceCapabilityPackages,
6531
+ materializeAbsoluteRemoteMacAgent,
3498
6532
  materializeAbsoluteMobileCompatibilityBundle,
3499
6533
  materializeAbsoluteMobileAssociationFiles,
3500
6534
  materializeAbsoluteCapacitorWebBundle,
3501
6535
  matchesAbsoluteMobileRoutePattern,
3502
6536
  loadAbsoluteNativeReleasePublisher,
3503
6537
  loadAbsoluteMobileMaterializedBundle,
6538
+ loadAbsoluteDeviceCapabilityProviders,
6539
+ listAbsoluteRemoteMacProfiles,
6540
+ isAbsoluteIosNativeRootInput,
6541
+ installAbsoluteRemoteMacAgent,
6542
+ installAbsoluteMobileSyncRemediation,
6543
+ installAbsoluteMobileAuthEnvironment,
6544
+ inspectAbsoluteRemoteMac,
3504
6545
  inspectAbsoluteMobileRouteMetadata,
3505
6546
  hashAbsoluteMobilePropsSchema,
3506
6547
  getCurrentAbsoluteMobileProducerContext,
6548
+ getAbsoluteRemoteMacProfile,
6549
+ getAbsoluteMobileSyncRemediation,
3507
6550
  fingerprintAbsoluteIosNativeProject,
6551
+ fingerprintAbsoluteIosDevProject,
3508
6552
  finalizeAbsoluteMobilePage,
3509
6553
  finalizeAbsoluteMobileCompatibilityBuild,
3510
6554
  fetchAbsoluteMobilePage,
6555
+ disposeAbsoluteMobilePage,
6556
+ discoverAbsoluteSyncSchema,
6557
+ discoverAbsoluteDeviceCapabilities,
6558
+ directAbsoluteProjectPackages,
6559
+ createAbsoluteRemoteIosDevProject,
3511
6560
  createAbsoluteMobileUpgradeResponse,
3512
6561
  createAbsoluteMobileRouteMetadataPlugin,
3513
6562
  createAbsoluteMobilePageRequest,
@@ -3517,20 +6566,32 @@ export {
3517
6566
  createAbsoluteMobileCompatibilityDispatcher,
3518
6567
  createAbsoluteMobileCompatibilityArtifact,
3519
6568
  createAbsoluteMobileBlobArtifactStore,
6569
+ createAbsoluteMobileAuthManifest,
3520
6570
  createAbsoluteMobileAssociationPlugin,
3521
6571
  createAbsoluteMobileAssociationDocuments,
6572
+ createAbsoluteIosNativeWatcher,
3522
6573
  carryForwardAbsoluteMobileCompatibilityReleases,
3523
6574
  captureAbsoluteMobileRouteGraph,
3524
6575
  buildAbsoluteMobileCompatibilityRelease,
3525
6576
  buildAbsoluteIosRelease,
3526
6577
  buildAbsoluteAndroidRelease,
6578
+ assertAbsoluteDeviceCapabilityPackages,
6579
+ applyAbsoluteNativeDeviceCapabilities,
3527
6580
  applyAbsoluteNativeDeepLinks,
3528
6581
  activateAbsoluteMobilePage,
3529
6582
  acceptsAbsoluteMobilePage,
6583
+ absoluteRemoteProjectSyncCommands,
6584
+ absoluteRemoteMacSshBase,
6585
+ absoluteDeviceNativeRequirements,
3530
6586
  MOBILE_PAGE_REQUEST_HEADERS,
3531
6587
  AbsoluteMobilePageProtocolError,
3532
6588
  APPLE_ASSOCIATION_PATH,
3533
6589
  ANDROID_ASSOCIATION_PATH,
6590
+ ABSOLUTE_SYNC_PACKAGE,
6591
+ ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
6592
+ ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
6593
+ ABSOLUTE_NATIVE_AUTH_SCOPES,
6594
+ ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
3534
6595
  ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
3535
6596
  ABSOLUTE_MOBILE_ROUTE_DETAIL,
3536
6597
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
@@ -3539,9 +6600,11 @@ export {
3539
6600
  ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
3540
6601
  ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
3541
6602
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
6603
+ ABSOLUTE_IOS_SIMULATOR_NAME,
3542
6604
  ABSOLUTE_IOS_RELEASE_FORMAT,
6605
+ ABSOLUTE_AUTH_PACKAGE,
3543
6606
  ABSOLUTE_ANDROID_RELEASE_FORMAT
3544
6607
  };
3545
6608
 
3546
- //# debugId=8F6E00755BF98A2E64756E2164756E21
6609
+ //# debugId=8F3479AD17C2968F64756E2164756E21
3547
6610
  //# sourceMappingURL=index.js.map