craft-native 0.0.451 → 0.0.464

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 (33) hide show
  1. package/dist/android/src/index.d.ts +8 -0
  2. package/dist/android/src/index.js +227 -30
  3. package/dist/android/src/testing.d.ts +1 -1
  4. package/dist/android/templates/CraftNativeBackground.kt.template +65 -25
  5. package/dist/android/templates/CraftNativeCapabilities.kt.template +3 -1
  6. package/dist/android/templates/CraftNativeMessagingService.kt.template +8 -28
  7. package/dist/android/templates/CraftNativeNotificationReceiver.kt.template +213 -15
  8. package/dist/android/templates/CraftNativeServices.kt.template +6 -4
  9. package/dist/android/templates/CraftNativeShell.kt.template +72 -21
  10. package/dist/android/templates/MainActivityNative.kt.template +227 -12
  11. package/dist/cli.js +1 -1
  12. package/dist/components/index.d.ts +18 -0
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/ios/src/index.d.ts +13 -14
  16. package/dist/ios/src/index.js +292 -81
  17. package/dist/ios/templates/CraftApp.swift +118 -16
  18. package/dist/ios/templates/CraftHybrid.swift +75 -5
  19. package/dist/ios/templates/CraftNativeActions.swift +84 -13
  20. package/dist/ios/templates/CraftNativeFiles.swift +83 -1
  21. package/dist/ios/templates/CraftNativeScreen.swift +130 -4
  22. package/dist/ios/templates/CraftNotificationService.swift +199 -0
  23. package/dist/ios/templates/CraftVoiceRecording.swift +29 -1
  24. package/dist/ios/templates/testing/DriverTests.swift.template +44 -5
  25. package/dist/mobile.d.ts +2 -0
  26. package/dist/mobile.js +120 -0
  27. package/dist/native-notifications.d.ts +17 -0
  28. package/dist/native-routing.d.ts +26 -0
  29. package/dist/testing/media-cache/media-cache.kt +28 -2
  30. package/dist/testing/media-cache/media-cache.swift.fixture +18 -4
  31. package/dist/typescript/src/native-notifications.d.ts +17 -0
  32. package/dist/typescript/src/native-routing.d.ts +26 -0
  33. package/package.json +1 -1
@@ -1,4 +1,212 @@
1
1
  // @bun
2
+ // src/native-notifications.ts
3
+ function nativeNotificationProblems(categories, nativeApp, renderer) {
4
+ if (categories === undefined)
5
+ return [];
6
+ if (!Array.isArray(categories))
7
+ return ["notificationCategories must be an array."];
8
+ const problems = [];
9
+ const categoryIds = new Set;
10
+ const object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
11
+ const text = (value) => typeof value === "string" && value.trim().length > 0;
12
+ for (const [index, category] of categories.entries()) {
13
+ const field = `notificationCategories[${index}]`;
14
+ if (!object(category) || !text(category.id)) {
15
+ problems.push(`${field} needs a nonempty id.`);
16
+ continue;
17
+ }
18
+ if (categoryIds.has(category.id))
19
+ problems.push(`${field}.id must be unique.`);
20
+ categoryIds.add(category.id);
21
+ if (!Array.isArray(category.actions) || category.actions.length < 1 || category.actions.length > 3) {
22
+ problems.push(`${field}.actions must contain 1\u20133 actions for both platforms.`);
23
+ continue;
24
+ }
25
+ const actionIds = new Set;
26
+ for (const [position, action] of category.actions.entries()) {
27
+ const path = `${field}.actions[${position}]`;
28
+ if (!object(action) || !text(action.id) || !text(action.title)) {
29
+ problems.push(`${path} needs a nonempty id and title.`);
30
+ continue;
31
+ }
32
+ if (action.id.startsWith("com.apple.") || actionIds.has(action.id))
33
+ problems.push(`${path}.id must be unique and must not use Apple's reserved prefix.`);
34
+ actionIds.add(action.id);
35
+ for (const flag of ["destructive", "foreground", "authenticationRequired"]) {
36
+ if (action[flag] !== undefined && typeof action[flag] !== "boolean")
37
+ problems.push(`${path}.${flag} must be a boolean.`);
38
+ }
39
+ if (!nativeApp?.trim() && (action.foreground !== true || renderer === "native"))
40
+ problems.push(`${path} requires nativeApp to receive native/background action events.`);
41
+ if (action.textInput !== undefined && (!object(action.textInput) || !text(action.textInput.buttonTitle) || action.textInput.placeholder !== undefined && typeof action.textInput.placeholder !== "string")) {
42
+ problems.push(`${path}.textInput needs a buttonTitle and an optional string placeholder.`);
43
+ }
44
+ }
45
+ }
46
+ return problems;
47
+ }
48
+
49
+ // src/native-routing.ts
50
+ function applyNativeParameterConstraints(params, rules) {
51
+ if (rules === undefined)
52
+ return true;
53
+ if (!rules || typeof rules !== "object" || Array.isArray(rules))
54
+ return false;
55
+ for (const [name, rule] of Object.entries(rules)) {
56
+ let value = params[name];
57
+ if (typeof value !== "string" || !rule || typeof rule !== "object" || Array.isArray(rule))
58
+ return false;
59
+ if (Object.keys(rule).some((key) => !["pattern", "exclude", "normalize"].includes(key)))
60
+ return false;
61
+ if (rule.normalize !== undefined && rule.normalize !== "lowercase")
62
+ return false;
63
+ if (rule.normalize === "lowercase")
64
+ value = value.toLowerCase();
65
+ if (rule.exclude !== undefined) {
66
+ if (!Array.isArray(rule.exclude) || rule.exclude.some((item) => typeof item !== "string"))
67
+ return false;
68
+ if (rule.exclude.some((item) => (rule.normalize === "lowercase" ? item.toLowerCase() : item) === value))
69
+ return false;
70
+ }
71
+ if (rule.pattern !== undefined) {
72
+ if (typeof rule.pattern !== "string")
73
+ return false;
74
+ try {
75
+ const matched = new RegExp(`^(?:${rule.pattern})$`).exec(value);
76
+ if (!matched || matched[0] !== value)
77
+ return false;
78
+ } catch {
79
+ return false;
80
+ }
81
+ }
82
+ params[name] = value;
83
+ }
84
+ return true;
85
+ }
86
+ function nativeRouteConstraintProblems(screens, constraints) {
87
+ const problems = [];
88
+ if (constraints === undefined)
89
+ return problems;
90
+ if (!constraints || typeof constraints !== "object" || Array.isArray(constraints))
91
+ return ["nativeScreenConstraints must name route parameter constraints."];
92
+ for (const [route, rules] of Object.entries(constraints)) {
93
+ const prefix = `nativeScreenConstraints[${JSON.stringify(route)}]`;
94
+ if (!Object.prototype.hasOwnProperty.call(screens ?? {}, route))
95
+ problems.push(`${prefix} names no native screen route.`);
96
+ if (!rules || typeof rules !== "object" || Array.isArray(rules)) {
97
+ problems.push(`${prefix} must name parameter constraints.`);
98
+ continue;
99
+ }
100
+ const names = route.split("/").filter((part) => part.startsWith(":")).map((part) => part.slice(1));
101
+ for (const [name, rule] of Object.entries(rules)) {
102
+ const field = `${prefix}[${JSON.stringify(name)}]`;
103
+ if (!names.includes(name))
104
+ problems.push(`${field} names no path parameter.`);
105
+ if (!rule || typeof rule !== "object" || Array.isArray(rule)) {
106
+ problems.push(`${field} must be a parameter constraint.`);
107
+ continue;
108
+ }
109
+ if (Object.keys(rule).some((key) => !["pattern", "exclude", "normalize"].includes(key)))
110
+ problems.push(`${field} has an unknown constraint field.`);
111
+ if (rule.exclude !== undefined && (!Array.isArray(rule.exclude) || rule.exclude.some((value) => typeof value !== "string")))
112
+ problems.push(`${field}.exclude must contain strings.`);
113
+ if (rule.normalize !== undefined && rule.normalize !== "lowercase")
114
+ problems.push(`${field}.normalize must be lowercase.`);
115
+ if (rule.pattern !== undefined) {
116
+ try {
117
+ if (typeof rule.pattern !== "string")
118
+ throw new Error("not a string");
119
+ new RegExp(`^(?:${rule.pattern})$`);
120
+ } catch {
121
+ problems.push(`${field}.pattern must be a valid regular expression.`);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ return problems;
127
+ }
128
+ function normalizeNativePath(raw) {
129
+ if (typeof raw !== "string")
130
+ return null;
131
+ let path = raw;
132
+ const cut = path.search(/[?#]/);
133
+ if (cut >= 0)
134
+ path = path.slice(0, cut);
135
+ if (!path.startsWith("/"))
136
+ return null;
137
+ path = path.replace(/\/{2,}/g, "/");
138
+ if (path.length > 1 && path.endsWith("/"))
139
+ path = path.slice(0, -1);
140
+ return path;
141
+ }
142
+ function routeSegments(path) {
143
+ return path === "/" ? [] : path.slice(1).split("/");
144
+ }
145
+ function matchNativePath(nativeScreens, raw, constraints) {
146
+ if (constraints !== undefined && (!constraints || typeof constraints !== "object" || Array.isArray(constraints)))
147
+ return null;
148
+ const path = normalizeNativePath(raw);
149
+ if (path === null)
150
+ return null;
151
+ const routes = Object.entries(nativeScreens ?? {}).map(([pattern, screen]) => ({ key: pattern, pattern: normalizeNativePath(pattern), screen })).filter((route) => route.pattern !== null && !!route.screen).map((route) => ({ ...route, segments: routeSegments(route.pattern) }));
152
+ const wild = (segments) => segments.at(-1) === "*" ? 1 : 0;
153
+ const fixed = (segments) => segments.filter((s) => s !== "*" && !s.startsWith(":")).length;
154
+ routes.sort((a, b) => wild(a.segments) - wild(b.segments) || fixed(b.segments) - fixed(a.segments) || b.segments.length - a.segments.length || (a.pattern < b.pattern ? -1 : a.pattern > b.pattern ? 1 : 0));
155
+ const decode = (value) => {
156
+ try {
157
+ return decodeURIComponent(value);
158
+ } catch {
159
+ return value;
160
+ }
161
+ };
162
+ const parts = routeSegments(path);
163
+ for (const route of routes) {
164
+ const params = {};
165
+ let matched = true;
166
+ for (let i = 0;i < route.segments.length; i++) {
167
+ const segment = route.segments[i];
168
+ if (segment === "*") {
169
+ params["*"] = parts.slice(i).join("/");
170
+ break;
171
+ }
172
+ if (i >= parts.length) {
173
+ matched = false;
174
+ break;
175
+ }
176
+ if (segment.startsWith(":")) {
177
+ if (!parts[i]) {
178
+ matched = false;
179
+ break;
180
+ }
181
+ params[segment.slice(1)] = decode(parts[i]);
182
+ } else if (segment !== parts[i]) {
183
+ matched = false;
184
+ break;
185
+ }
186
+ }
187
+ if (!matched)
188
+ continue;
189
+ if (route.segments.at(-1) !== "*" && route.segments.length !== parts.length)
190
+ continue;
191
+ if (!applyNativeParameterConstraints(params, constraints?.[route.key]))
192
+ continue;
193
+ const query = raw.indexOf("?");
194
+ if (query >= 0) {
195
+ for (const item of raw.slice(query + 1).split("#")[0].split("&")) {
196
+ if (!item)
197
+ continue;
198
+ const [rawKey = "", ...rest] = item.split("=");
199
+ const key = decode(rawKey);
200
+ if (!key || key in params)
201
+ continue;
202
+ params[key] = decode(rest.join("=").replace(/\+/g, " "));
203
+ }
204
+ }
205
+ return { path, screen: route.screen, params };
206
+ }
207
+ return null;
208
+ }
209
+
2
210
  // ../ios/src/index.ts
3
211
  import { gzipSync } from "zlib";
4
212
 
@@ -479,83 +687,6 @@ function resolveCraftVersion(startDir = import.meta.dir) {
479
687
  return "0.0.0";
480
688
  }
481
689
  var CRAFT_VERSION = resolveCraftVersion();
482
- function normalizeNativePath(raw) {
483
- if (typeof raw !== "string")
484
- return null;
485
- let path = raw;
486
- const cut = path.search(/[?#]/);
487
- if (cut >= 0)
488
- path = path.slice(0, cut);
489
- if (!path.startsWith("/"))
490
- return null;
491
- path = path.replace(/\/{2,}/g, "/");
492
- if (path.length > 1 && path.endsWith("/"))
493
- path = path.slice(0, -1);
494
- return path;
495
- }
496
- function routeSegments(path) {
497
- return path === "/" ? [] : path.slice(1).split("/");
498
- }
499
- function matchNativePath(nativeScreens, raw) {
500
- const path = normalizeNativePath(raw);
501
- if (path === null)
502
- return null;
503
- const routes = Object.entries(nativeScreens ?? {}).map(([pattern, screen]) => ({ pattern: normalizeNativePath(pattern), screen })).filter((route) => route.pattern !== null && !!route.screen).map((route) => ({ ...route, segments: routeSegments(route.pattern) }));
504
- const wild = (segments) => segments.at(-1) === "*" ? 1 : 0;
505
- const fixed = (segments) => segments.filter((s) => s !== "*" && !s.startsWith(":")).length;
506
- routes.sort((a, b) => wild(a.segments) - wild(b.segments) || fixed(b.segments) - fixed(a.segments) || b.segments.length - a.segments.length || (a.pattern < b.pattern ? -1 : a.pattern > b.pattern ? 1 : 0));
507
- const decode = (value) => {
508
- try {
509
- return decodeURIComponent(value);
510
- } catch {
511
- return value;
512
- }
513
- };
514
- const parts = routeSegments(path);
515
- for (const route of routes) {
516
- const params = {};
517
- let matched = true;
518
- for (let i = 0;i < route.segments.length; i++) {
519
- const segment = route.segments[i];
520
- if (segment === "*") {
521
- params["*"] = parts.slice(i).join("/");
522
- break;
523
- }
524
- if (i >= parts.length) {
525
- matched = false;
526
- break;
527
- }
528
- if (segment.startsWith(":")) {
529
- if (!parts[i]) {
530
- matched = false;
531
- break;
532
- }
533
- params[segment.slice(1)] = decode(parts[i]);
534
- } else if (segment !== parts[i]) {
535
- matched = false;
536
- break;
537
- }
538
- }
539
- if (!matched)
540
- continue;
541
- if (route.segments.at(-1) !== "*" && route.segments.length !== parts.length)
542
- continue;
543
- const query = raw.indexOf("?");
544
- if (query >= 0) {
545
- for (const item of raw.slice(query + 1).split("#")[0].split("&")) {
546
- if (!item)
547
- continue;
548
- const [rawKey = "", ...rest] = item.split("=");
549
- const key = decode(rawKey);
550
- if (!key || key in params)
551
- continue;
552
- params[key] = decode(rest.join("=").replace(/\+/g, " "));
553
- }
554
- }
555
- return { path, screen: route.screen, params };
556
- }
557
- return null;
558
- }
559
690
  function isHybrid(config) {
560
691
  return (config.renderer ?? "web") === "web" && Object.keys(config.nativeScreens ?? {}).length > 0;
561
692
  }
@@ -576,8 +707,9 @@ function nativeStartPath(config) {
576
707
  }
577
708
  }
578
709
  function hybridConfigProblems(config) {
579
- const problems = [];
710
+ const problems = nativeNotificationProblems(config.notificationCategories, config.nativeApp, config.renderer);
580
711
  const screens = Object.entries(config.nativeScreens ?? {});
712
+ problems.push(...nativeRouteConstraintProblems(config.nativeScreens, config.nativeScreenConstraints));
581
713
  for (const [pattern, screen] of screens) {
582
714
  if (normalizeNativePath(pattern) !== pattern || /[?#]/.test(pattern))
583
715
  problems.push(`nativeScreens path "${pattern}" must be an absolute path with no query, hash or trailing slash.`);
@@ -601,11 +733,11 @@ function hybridConfigProblems(config) {
601
733
  }
602
734
  if (hasNativeRoutes(config)) {
603
735
  for (const id of ids) {
604
- if (!matchNativePath(config.nativeScreens, id))
736
+ if (!matchNativePath(config.nativeScreens, id, config.nativeScreenConstraints))
605
737
  problems.push(`Tab "${id}" is not a nativeScreens path; with renderer: "native" every tab must be a native screen.`);
606
738
  }
607
739
  const start = nativeStartPath(config);
608
- if (!matchNativePath(config.nativeScreens, start))
740
+ if (!matchNativePath(config.nativeScreens, start, config.nativeScreenConstraints))
609
741
  problems.push(`The start path "${start}" (from devServerURL) is not a nativeScreens path; with renderer: "native" the app must start on a native screen.`);
610
742
  }
611
743
  if (config.nativeApp !== undefined) {
@@ -889,6 +1021,78 @@ function validateHealthKitBackground(config) {
889
1021
  if (!["immediate", "hourly", "daily", "weekly"].includes(config.healthKitBackgroundFrequency ?? "hourly"))
890
1022
  throw new Error("healthKitBackgroundFrequency must be immediate, hourly, daily or weekly");
891
1023
  }
1024
+ function validateNotificationAttachments(config) {
1025
+ if (config.enableNotificationAttachments !== undefined && typeof config.enableNotificationAttachments !== "boolean")
1026
+ throw new Error("enableNotificationAttachments must be a boolean");
1027
+ if (config.enableNotificationAttachments && config.enablePushNotifications !== true)
1028
+ throw new Error("enableNotificationAttachments requires enablePushNotifications");
1029
+ }
1030
+ var notificationServiceInclude = ` - path: CraftNotificationService.yml
1031
+ `;
1032
+ function syncNotificationService(output, config) {
1033
+ validateNotificationAttachments(config);
1034
+ const spec = join4(output, "CraftNotificationService.yml");
1035
+ if (!config.enableNotificationAttachments) {
1036
+ writeFileSync3(spec, `{}
1037
+ `);
1038
+ return;
1039
+ }
1040
+ const name = productName(config.appName);
1041
+ const folder = join4(output, "NotificationService");
1042
+ mkdirSync2(folder, { recursive: true });
1043
+ const source = readFileSync3(join4(TEMPLATES_DIR, "CraftNotificationService.swift"), "utf8");
1044
+ const destination = join4(folder, "CraftNotificationService.swift");
1045
+ if (!existsSync2(destination) || readFileSync3(destination, "utf8") !== source)
1046
+ writeFileSync3(destination, source);
1047
+ writeFileSync3(join4(folder, "Info.plist"), `<?xml version="1.0" encoding="UTF-8"?>
1048
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1049
+ <plist version="1.0"><dict>
1050
+ <key>CFBundleExecutable</key><string>$(EXECUTABLE_NAME)</string>
1051
+ <key>CFBundleIdentifier</key><string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
1052
+ <key>CFBundleName</key><string>$(PRODUCT_NAME)</string>
1053
+ <key>CFBundlePackageType</key><string>XPC!</string>
1054
+ <key>CFBundleShortVersionString</key><string>$(MARKETING_VERSION)</string>
1055
+ <key>CFBundleVersion</key><string>$(CURRENT_PROJECT_VERSION)</string>
1056
+ <key>NSExtension</key><dict>
1057
+ <key>NSExtensionPointIdentifier</key><string>com.apple.usernotifications.service</string>
1058
+ <key>NSExtensionPrincipalClass</key><string>$(PRODUCT_MODULE_NAME).CraftNotificationService</string>
1059
+ </dict>
1060
+ </dict></plist>
1061
+ `);
1062
+ writeFileSync3(spec, `targets:
1063
+ ${name}:
1064
+ dependencies:
1065
+ - target: ${name}NotificationService
1066
+ embed: true
1067
+ ${name}NotificationService:
1068
+ type: app-extension
1069
+ platform: iOS
1070
+ deploymentTarget: "${config.iosVersion || "15.0"}"
1071
+ sources:
1072
+ - NotificationService
1073
+ settings:
1074
+ INFOPLIST_FILE: NotificationService/Info.plist
1075
+ PRODUCT_NAME: "$(TARGET_NAME)"
1076
+ PRODUCT_BUNDLE_IDENTIFIER: ${config.bundleId}.notificationservice
1077
+ SWIFT_VERSION: "5.0"
1078
+ APPLICATION_EXTENSION_API_ONLY: YES
1079
+ SKIP_INSTALL: YES
1080
+ `);
1081
+ const project = join4(output, "project.yml");
1082
+ if (existsSync2(project)) {
1083
+ const text = readFileSync3(project, "utf8");
1084
+ const include = Bun.YAML.parse(text).include;
1085
+ const includes = Array.isArray(include) ? include : [include];
1086
+ const present = includes.some((entry) => entry === "CraftNotificationService.yml" || entry && typeof entry === "object" && ("path" in entry) && entry.path === "CraftNotificationService.yml");
1087
+ if (!present) {
1088
+ if (/^include:/m.test(text) && !/^include:[ \t]*\r?\n[ \t]+-/m.test(text))
1089
+ throw new Error("Add a CraftNotificationService.yml entry to the project.yml include list");
1090
+ writeFileSync3(project, /^include:/m.test(text) ? text.replace(/^include:[ \t]*\r?\n/m, `include:
1091
+ ${notificationServiceInclude}`) : `include:
1092
+ ${notificationServiceInclude}${text}`);
1093
+ }
1094
+ }
1095
+ }
892
1096
  function renderEntitlements(config) {
893
1097
  validateHealthKitBackground(config);
894
1098
  const entries = [];
@@ -1375,6 +1579,7 @@ function restoreShell(output) {
1375
1579
  writeFileSync3(file, JSON.stringify(config, null, 2));
1376
1580
  }
1377
1581
  function writeShell(output, config) {
1582
+ validateNotificationAttachments(config);
1378
1583
  if (!config.appName || !/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(config.bundleId))
1379
1584
  throw new Error("The iOS shell needs an appName and a valid bundleId in craft.config.json.");
1380
1585
  const writeSource = (path, source) => {
@@ -1422,6 +1627,7 @@ function writeShell(output, config) {
1422
1627
  const source = readFileSync3(join4(TEMPLATES_DIR, template), "utf8").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{HEALTH_KIT_ENABLED\}\}/g, config.enableHealthKit ? "true" : "false");
1423
1628
  writeSource(join4(output, folder, filename), source);
1424
1629
  }
1630
+ syncNotificationService(output, config);
1425
1631
  }
1426
1632
  function refreshUsageDescriptions(output, config) {
1427
1633
  const path = join4(output, "Info.plist");
@@ -1462,6 +1668,7 @@ async function init(options) {
1462
1668
  ...given
1463
1669
  };
1464
1670
  validateHealthKitBackground(config);
1671
+ validateNotificationAttachments(config);
1465
1672
  if (config.renderer !== "web" && config.renderer !== "native") {
1466
1673
  throw new Error(`Unknown iOS renderer: ${config.renderer}. Expected web or native.`);
1467
1674
  }
@@ -1563,7 +1770,8 @@ ${plistArray(nativeFontFiles, 2)}
1563
1770
  ${nativeDependencies.join(`
1564
1771
  `)}` : "").replace(/\{\{NATIVE_TARGETS\}\}/g, nativeTargets.join(`
1565
1772
  `));
1566
- writeFileSync3(join4(output, "project.yml"), projectYml);
1773
+ writeFileSync3(join4(output, "project.yml"), `include:
1774
+ ${notificationServiceInclude}${projectYml}`);
1567
1775
  writeFileSync3(join4(output, "Craft.entitlements"), renderEntitlements(config));
1568
1776
  writeFileSync3(join4(output, "PrivacyInfo.xcprivacy"), renderPrivacyManifest(config));
1569
1777
  renderAssetCatalog(output, config);
@@ -1613,6 +1821,7 @@ async function build(options) {
1613
1821
  throw new Error(`No craft.config.json found in ${output}. Run 'craft ios init' first.`);
1614
1822
  }
1615
1823
  const config = JSON.parse(readFileSync3(configPath, "utf-8"));
1824
+ validateNotificationAttachments(config);
1616
1825
  if (config.renderer !== undefined && config.renderer !== "web" && config.renderer !== "native") {
1617
1826
  throw new Error(`Unknown iOS renderer: ${config.renderer}. Expected web or native.`);
1618
1827
  }
@@ -1802,6 +2011,7 @@ async function run(options) {
1802
2011
  console.log(" 4. Click Run (\u25B6\uFE0F)");
1803
2012
  }
1804
2013
  }
2014
+ var nativeRouteConstraints = true;
1805
2015
  export {
1806
2016
  LAUNCH_LOGO,
1807
2017
  assertNoApplicationSwiftSources,
@@ -1821,6 +2031,7 @@ export {
1821
2031
  matchNativePath,
1822
2032
  nativeMessages,
1823
2033
  nativeRouteBundles,
2034
+ nativeRouteConstraints,
1824
2035
  nativeRouteRuntime,
1825
2036
  nativeSensorCapabilities,
1826
2037
  nativeStartPath,