@lowdefy/shell 3.23.3 → 7.2.0

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 (356) hide show
  1. package/LICENSE +183 -183
  2. package/README.md +364 -2
  3. package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
  4. package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
  5. package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
  6. package/app/+native-intent.js +39 -0
  7. package/app/_layout.js +36 -0
  8. package/app.config.ts +183 -0
  9. package/appLinks.js +65 -0
  10. package/applyShellPlugins.js +43 -0
  11. package/authCallbackIntentFilters.js +46 -0
  12. package/babel.config.js +25 -0
  13. package/checkShellConfig.js +172 -0
  14. package/checkShellPlugins.js +74 -0
  15. package/getPrivacyManifest.js +39 -0
  16. package/metro.config.js +73 -0
  17. package/native/LowdefyAppShortcuts.swift.template +84 -0
  18. package/native/withAppShortcuts.js +60 -0
  19. package/native-fingerprint.json +16 -0
  20. package/package.json +120 -40
  21. package/profiles.js +607 -0
  22. package/readShellConfig.js +36 -0
  23. package/resolveShellPlugins.js +109 -0
  24. package/runtimeVersion.js +112 -0
  25. package/scripts/checkReleaseBundle.mjs +358 -0
  26. package/scripts/createDayjsLocales.mjs +67 -0
  27. package/scripts/createShellAssets.mjs +54 -0
  28. package/scripts/createShellVersion.mjs +83 -0
  29. package/scripts/createTypeManifest.mjs +205 -0
  30. package/shell.dev.json +11 -0
  31. package/shellVariant.js +126 -0
  32. package/src/ShellContext.js +32 -0
  33. package/src/actions/CopyToClipboard.js +29 -0
  34. package/src/actions/CopyToClipboard.test.js +33 -0
  35. package/src/actions/GeolocationCurrentPosition.js +61 -0
  36. package/src/actions/GeolocationCurrentPosition.test.js +79 -0
  37. package/src/actions/GetLocalStorage.js +36 -0
  38. package/src/actions/RemoveLocalStorage.js +31 -0
  39. package/src/actions/ScrollTo.js +66 -0
  40. package/src/actions/ScrollTo.test.js +83 -0
  41. package/src/actions/SetFocus.js +33 -0
  42. package/src/actions/SetFocus.test.js +49 -0
  43. package/src/actions/SetLocalStorage.js +34 -0
  44. package/src/actions/localStorage.test.js +68 -0
  45. package/src/actions/measureInScrollView.js +36 -0
  46. package/src/actions/nativeActions.js +41 -0
  47. package/src/auth/AuthButton.js +44 -0
  48. package/src/auth/AuthGate.js +57 -0
  49. package/src/auth/AuthLink.js +43 -0
  50. package/src/auth/LockScreen.js +67 -0
  51. package/src/auth/LockScreen.test.js +37 -0
  52. package/src/auth/SignInScreen.js +268 -0
  53. package/src/auth/SignInScreen.test.js +187 -0
  54. package/src/auth/createAppAuth.js +325 -0
  55. package/src/auth/createAppAuth.test.js +424 -0
  56. package/src/auth/createAuthApi.js +115 -0
  57. package/src/auth/createAuthApi.test.js +138 -0
  58. package/src/auth/createCoreAuth.js +96 -0
  59. package/src/auth/createCoreAuth.test.js +188 -0
  60. package/src/auth/createDeviceKey.js +62 -0
  61. package/src/auth/createDeviceKey.test.js +54 -0
  62. package/src/auth/createSessionStores.js +45 -0
  63. package/src/auth/createTokenStore.js +103 -0
  64. package/src/auth/createTokenStore.test.js +116 -0
  65. package/src/auth/getAuthSettings.js +24 -0
  66. package/src/auth/getAuthView.js +39 -0
  67. package/src/auth/getAuthView.test.js +52 -0
  68. package/src/auth/getSheetReturnUrl.js +35 -0
  69. package/src/auth/getTokenStoreOptions.js +46 -0
  70. package/src/auth/isBackgroundShell.js +26 -0
  71. package/src/auth/isSheetReturnLink.js +35 -0
  72. package/src/auth/parseQuery.js +39 -0
  73. package/src/auth/purgeAppData.js +80 -0
  74. package/src/auth/purgeAppData.test.js +104 -0
  75. package/src/auth/shouldLock.js +26 -0
  76. package/src/auth/signInWithSheet.js +97 -0
  77. package/src/auth/signInWithSheet.test.js +205 -0
  78. package/src/auth/styles.js +78 -0
  79. package/src/auth/unregisterPushToken.js +62 -0
  80. package/src/auth/unregisterPushToken.test.js +67 -0
  81. package/src/auth/usesBiometricUnlock.js +23 -0
  82. package/src/auth/watchSession.js +47 -0
  83. package/src/auth/withoutCookies.js +26 -0
  84. package/src/cache/createSqliteCacheStore.js +113 -0
  85. package/src/cache/createSqliteCacheStore.test.js +93 -0
  86. package/src/cache/getCacheKey.js +25 -0
  87. package/src/cache/getLastCacheKeyName.js +22 -0
  88. package/src/components/AppProvider.js +118 -0
  89. package/src/components/Frame.js +116 -0
  90. package/src/components/Frame.test.js +101 -0
  91. package/src/components/LoadingScreen.js +35 -0
  92. package/src/components/MenuButton.js +121 -0
  93. package/src/components/MenuButton.test.js +74 -0
  94. package/src/components/MessageScreen.js +51 -0
  95. package/src/components/NavigationTheme.js +33 -0
  96. package/src/components/NoConnectionBanner.js +51 -0
  97. package/src/components/NoConnectionBanner.test.js +73 -0
  98. package/src/components/OtherPageScreen.js +95 -0
  99. package/src/components/OtherPageScreen.test.js +74 -0
  100. package/src/components/PageScreen.js +254 -0
  101. package/src/components/PageScreen.test.js +492 -0
  102. package/src/components/RouteErrorScreen.js +47 -0
  103. package/src/components/ShellRoot.js +34 -0
  104. package/src/components/ShellRoot.test.js +62 -0
  105. package/src/components/ShellThemeProvider.js +45 -0
  106. package/src/components/SwitchToTab.js +36 -0
  107. package/src/components/SwitchToTab.test.js +63 -0
  108. package/src/components/TabIconImages.js +72 -0
  109. package/src/components/UpdateScreen.js +40 -0
  110. package/src/components/getNavigationTheme.js +44 -0
  111. package/src/components/getNavigationTheme.test.js +41 -0
  112. package/src/components/styles.js +50 -0
  113. package/src/components/useSurfaceTokens.js +47 -0
  114. package/src/config/getAppApiBase.js +34 -0
  115. package/src/config/getShellConfig.js +37 -0
  116. package/src/config/getShellConfig.test.js +80 -0
  117. package/src/createShellApp.js +126 -0
  118. package/src/dev/DevShell.js +239 -0
  119. package/src/dev/DevShell.test.js +233 -0
  120. package/src/dev/connect/ConfirmSheet.js +71 -0
  121. package/src/dev/connect/ConnectScreen.js +117 -0
  122. package/src/dev/connect/QrScanner.js +89 -0
  123. package/src/dev/connection/createDevShellConfig.js +47 -0
  124. package/src/dev/connection/createDevTransport.js +51 -0
  125. package/src/dev/connection/createDevTransport.test.js +87 -0
  126. package/src/dev/connection/createServerList.js +48 -0
  127. package/src/dev/connection/isLoopbackHost.js +25 -0
  128. package/src/dev/connection/pairedShellApp.test.js +104 -0
  129. package/src/dev/connection/parseDevLink.js +66 -0
  130. package/src/dev/connection/parseDevLink.test.js +97 -0
  131. package/src/dev/connection/parseQuery.js +33 -0
  132. package/src/dev/connection/parseServerInput.js +40 -0
  133. package/src/dev/connection/parseServerUrl.js +45 -0
  134. package/src/dev/devStyles.js +97 -0
  135. package/src/dev/index.js +21 -0
  136. package/src/dev/inject/createDeviceInjections.js +188 -0
  137. package/src/dev/inject/createDeviceInjections.test.js +224 -0
  138. package/src/dev/inject/createOfflineFetch.js +35 -0
  139. package/src/dev/inject/createOfflineFetch.test.js +68 -0
  140. package/src/dev/inject/injectPush.js +60 -0
  141. package/src/dev/inject/injectShare.js +55 -0
  142. package/src/dev/inject/withInjectedDevice.js +98 -0
  143. package/src/dev/menu/BlockGallery.js +33 -0
  144. package/src/dev/menu/DevMenu.js +121 -0
  145. package/src/dev/menu/createLogBuffer.js +83 -0
  146. package/src/dev/menu/watchShake.js +52 -0
  147. package/src/dev/menu/watchShake.test.js +61 -0
  148. package/src/dev/recorder/createNativeRecorder.js +204 -0
  149. package/src/dev/recorder/createNativeRecorder.test.js +307 -0
  150. package/src/dev/redirectDevLink.js +30 -0
  151. package/src/dev/redirectDevLink.test.js +49 -0
  152. package/src/dev/steps/createStepRunner.js +184 -0
  153. package/src/dev/steps/createStepRunner.test.js +262 -0
  154. package/src/dev/steps/findBlock.js +78 -0
  155. package/src/dev/steps/isContextSettled.js +37 -0
  156. package/src/dev/steps/waitForSettle.js +39 -0
  157. package/src/dev/steps/waitUntil.js +40 -0
  158. package/src/dev/tab/DevTabClient.js +97 -0
  159. package/src/dev/tab/answerEval.js +39 -0
  160. package/src/dev/tab/answerInspect.js +45 -0
  161. package/src/dev/tab/createDevTabClient.js +272 -0
  162. package/src/dev/tab/createDevTabClient.test.js +317 -0
  163. package/src/dev/tab/describeTabError.js +35 -0
  164. package/src/dev/tab/devClientMarker.js +22 -0
  165. package/src/dev/tab/devClientMarker.test.js +23 -0
  166. package/src/dev/tab/findPageContext.js +43 -0
  167. package/src/dev/tab/getShownPage.js +42 -0
  168. package/src/dev/tab/readEventStream.js +68 -0
  169. package/src/dev/tab/reloadApp.js +42 -0
  170. package/src/dev/tab/reloadApp.test.js +115 -0
  171. package/src/device/DeviceHosts.js +32 -0
  172. package/src/device/basic/createFilesFeature.js +41 -0
  173. package/src/device/basic/createHapticsFeature.js +46 -0
  174. package/src/device/basic/createKeepAwakeFeature.js +37 -0
  175. package/src/device/basic/createPhotosFeature.js +40 -0
  176. package/src/device/basic/createRequestQueueFeature.js +37 -0
  177. package/src/device/basic/createShareFeature.js +63 -0
  178. package/src/device/basic/createShareFeature.test.js +47 -0
  179. package/src/device/basic/createSpeechFeature.js +43 -0
  180. package/src/device/basic/createUploadFeature.js +41 -0
  181. package/src/device/basic/toImagePickerOptions.js +41 -0
  182. package/src/device/basic/toPickedFile.js +54 -0
  183. package/src/device/calendar/index.js +68 -0
  184. package/src/device/contacts/index.js +67 -0
  185. package/src/device/createDevice.js +65 -0
  186. package/src/device/device.js +27 -0
  187. package/src/device/field/MapView.js +93 -0
  188. package/src/device/field/ScanCodeSheet.js +86 -0
  189. package/src/device/field/ScannerView.js +52 -0
  190. package/src/device/field/createAudioFeature.js +70 -0
  191. package/src/device/field/createBrightnessFeature.js +40 -0
  192. package/src/device/field/createCameraFeature.js +56 -0
  193. package/src/device/field/createDocumentScannerFeature.js +51 -0
  194. package/src/device/field/createLocationFeature.js +39 -0
  195. package/src/device/field/createScanRequests.js +55 -0
  196. package/src/device/field/index.js +39 -0
  197. package/src/device/field/scanRequests.js +22 -0
  198. package/src/device/injected.js +60 -0
  199. package/src/device/injected.test.js +39 -0
  200. package/src/device/nfc/index.js +89 -0
  201. package/src/device/nfc/toNfcRecord.js +44 -0
  202. package/src/device/nfc/toNfcRecord.test.js +48 -0
  203. package/src/device/tracking/createBacklog.js +63 -0
  204. package/src/device/tracking/createBacklog.test.js +47 -0
  205. package/src/device/tracking/index.js +211 -0
  206. package/src/device/tracking/index.test.js +110 -0
  207. package/src/host/createDeviceStore.js +32 -0
  208. package/src/host/createMemoryStore.js +32 -0
  209. package/src/host/createNetworkStatus.js +94 -0
  210. package/src/host/createNetworkStatus.test.js +94 -0
  211. package/src/host/createRouterAdapter.js +33 -0
  212. package/src/host/createShellFetch.js +62 -0
  213. package/src/host/createShellFetch.test.js +165 -0
  214. package/src/host/createShellGlobals.js +62 -0
  215. package/src/host/createShellHost.js +83 -0
  216. package/src/host/createShellHost.test.js +36 -0
  217. package/src/host/getOrigin.js +31 -0
  218. package/src/host/isNetworkError.js +24 -0
  219. package/src/host/isOnline.js +22 -0
  220. package/src/host/networkStatus.js +24 -0
  221. package/src/host/readServerError.js +45 -0
  222. package/src/host/readServerError.test.js +85 -0
  223. package/src/host/reportShellError.js +47 -0
  224. package/src/host/reportShellError.test.js +79 -0
  225. package/src/host/withNetworkErrors.js +38 -0
  226. package/src/host/withNetworkErrors.test.js +61 -0
  227. package/src/host/withShellHeader.js +25 -0
  228. package/src/host/withShellHeader.test.js +28 -0
  229. package/src/js/evaluateJsEntries.js +43 -0
  230. package/src/js/evaluateJsEntries.test.js +48 -0
  231. package/src/locale/createShellLocale.js +112 -0
  232. package/src/locale/createShellLocale.test.js +74 -0
  233. package/src/locale/dayjsLocales.js +165 -0
  234. package/src/locale/getDeviceLocales.js +23 -0
  235. package/src/locale/loadDayjsLocale.js +32 -0
  236. package/src/locale/resolveActiveLocale.js +46 -0
  237. package/src/locale/resolveActiveLocale.test.js +40 -0
  238. package/src/native/backgroundFetch.js +97 -0
  239. package/src/native/callEndpoint.js +63 -0
  240. package/src/native/createAppEventTrigger.js +70 -0
  241. package/src/native/currentApp.js +49 -0
  242. package/src/native/getPagePath.js +32 -0
  243. package/src/native/openPath.js +55 -0
  244. package/src/native/openPath.test.js +39 -0
  245. package/src/native/parseShellLink.js +38 -0
  246. package/src/native/parseShellLink.test.js +29 -0
  247. package/src/native/startAppServices.js +94 -0
  248. package/src/native/startAppServices.test.js +97 -0
  249. package/src/native/startShareIn.js +59 -0
  250. package/src/native/startShortcuts.js +88 -0
  251. package/src/native/startShortcuts.test.js +59 -0
  252. package/src/native/toSharePayload.js +47 -0
  253. package/src/native/toSharePayload.test.js +47 -0
  254. package/src/native/whenAppReady.js +48 -0
  255. package/src/navigation/SlotContext.js +24 -0
  256. package/src/navigation/frameSlots.js +27 -0
  257. package/src/navigation/getFrame.js +30 -0
  258. package/src/navigation/getInitialScreenOptions.js +63 -0
  259. package/src/navigation/getInitialScreenOptions.test.js +66 -0
  260. package/src/navigation/getPresentation.js +28 -0
  261. package/src/navigation/getSlotRootPath.js +27 -0
  262. package/src/navigation/getStackOptions.js +185 -0
  263. package/src/navigation/getTabHref.js +45 -0
  264. package/src/navigation/getTabIcons.js +43 -0
  265. package/src/navigation/isPageUnderHeader.js +46 -0
  266. package/src/navigation/navigation.test.js +342 -0
  267. package/src/navigation/toAppLinkPath.js +43 -0
  268. package/src/navigation/toAppLinkPath.test.js +53 -0
  269. package/src/navigation/toRouterHref.js +29 -0
  270. package/src/notifications/createNotificationsFeature.js +61 -0
  271. package/src/notifications/getCategoryAction.js +31 -0
  272. package/src/notifications/handleNotificationResponse.js +82 -0
  273. package/src/notifications/registerPushToken.js +71 -0
  274. package/src/notifications/startNotifications.js +114 -0
  275. package/src/notifications/toCategoryActions.js +42 -0
  276. package/src/notifications/toNotificationEvent.js +36 -0
  277. package/src/notifications/toScheduleRequest.js +62 -0
  278. package/src/plugins/appTypes.js +28 -0
  279. package/src/plugins/prefixTypes.js +28 -0
  280. package/src/plugins/shellPlugins.json +15 -0
  281. package/src/plugins/typeManifest.js +789 -0
  282. package/src/plugins/types.js +64 -0
  283. package/src/plugins/types.test.js +173 -0
  284. package/src/polyfills/installGetRandomValues.js +28 -0
  285. package/src/queue/createRequestQueue.js +198 -0
  286. package/src/queue/createRequestQueue.test.js +215 -0
  287. package/src/queue/createSqliteQueueStore.js +121 -0
  288. package/src/queue/summarizeQueue.js +39 -0
  289. package/src/session/createAppSession.js +323 -0
  290. package/src/session/createAppSession.test.js +391 -0
  291. package/src/session/getPageView.js +59 -0
  292. package/src/session/getPageView.test.js +137 -0
  293. package/src/sync/encodePagePath.js +27 -0
  294. package/src/sync/fetchPage.js +72 -0
  295. package/src/sync/fetchPage.test.js +84 -0
  296. package/src/sync/fetchRoot.js +43 -0
  297. package/src/sync/fetchUser.js +33 -0
  298. package/src/sync/runLimited.js +36 -0
  299. package/src/sync/toRootConfig.js +35 -0
  300. package/src/test/createFakeNetwork.js +40 -0
  301. package/src/test/createFakeSecureStore.js +40 -0
  302. package/src/test/createFakeServer.js +98 -0
  303. package/src/test/createMemoryCacheStore.js +59 -0
  304. package/src/test/createNetworkError.js +24 -0
  305. package/src/test/expoImage.js +42 -0
  306. package/src/test/expoNotifications.js +40 -0
  307. package/src/test/expoQuickActions.js +22 -0
  308. package/src/test/expoSpeechRecognition.js +31 -0
  309. package/src/test/expoTaskManager.js +25 -0
  310. package/src/test/expoUi.js +55 -0
  311. package/src/test/expoUiBottomSheet.js +64 -0
  312. package/src/test/expoUiDateTimePicker.js +26 -0
  313. package/src/test/expoUiMenu.js +46 -0
  314. package/src/test/expoUiSegmentedControl.js +47 -0
  315. package/src/test/expoUiSwiftUi.js +62 -0
  316. package/src/test/expoUiSwiftUiModifiers.js +23 -0
  317. package/src/test/expoWebBrowser.js +23 -0
  318. package/src/test/fixtures.js +98 -0
  319. package/src/test/nativeModule.js +33 -0
  320. package/src/test/renderPageScreen.js +109 -0
  321. package/src/types/checkTypeSet.js +39 -0
  322. package/src/types/collectPageTypes.js +49 -0
  323. package/src/types/collectPageTypes.test.js +90 -0
  324. package/src/types/findMissingPageTypes.js +40 -0
  325. package/src/version/compareVersions.js +38 -0
  326. package/src/version/isShellTooOld.js +30 -0
  327. package/src/version/shellVersion.js +21 -0
  328. package/src/version/shellVersion.test.js +32 -0
  329. package/src/widgets/fetchWidgetData.js +59 -0
  330. package/src/widgets/index.js +156 -0
  331. package/src/widgets/refreshWidgets.js +31 -0
  332. package/withExcludedNativeModules.js +93 -0
  333. package/dist/location.js +0 -33
  334. package/dist/public/apple-touch-icon.png +0 -0
  335. package/dist/public/icon-32.png +0 -0
  336. package/dist/public/icon-512.png +0 -0
  337. package/dist/public/icon.svg +0 -17
  338. package/dist/public/logo-dark-theme.png +0 -0
  339. package/dist/public/logo-light-theme.png +0 -0
  340. package/dist/public/logo-square-dark-theme.png +0 -0
  341. package/dist/public/logo-square-light-theme.png +0 -0
  342. package/dist/public/manifest.webmanifest +0 -16
  343. package/dist/shell/246.93432ff68186aae6c87b.js +0 -2
  344. package/dist/shell/246.93432ff68186aae6c87b.js.LICENSE.txt +0 -8
  345. package/dist/shell/454.b597ca21366afda000af.js +0 -2
  346. package/dist/shell/454.b597ca21366afda000af.js.LICENSE.txt +0 -14
  347. package/dist/shell/484.d645e6c09079f45ce044.js +0 -1
  348. package/dist/shell/706.a66931e02a549fd269bd.js +0 -2
  349. package/dist/shell/706.a66931e02a549fd269bd.js.LICENSE.txt +0 -8
  350. package/dist/shell/9.78be8c1b6c3986f404fc.js +0 -2
  351. package/dist/shell/9.78be8c1b6c3986f404fc.js.LICENSE.txt +0 -45
  352. package/dist/shell/922.43b7a3ebe22ef05b3049.js +0 -2
  353. package/dist/shell/922.43b7a3ebe22ef05b3049.js.LICENSE.txt +0 -14
  354. package/dist/shell/index.html +0 -57
  355. package/dist/shell/main.f01501a75909f38ccc32.js +0 -1
  356. package/dist/shell/runtime.e50f6c3a5f7c74a0a717.js +0 -1
package/profiles.js ADDED
@@ -0,0 +1,607 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ const path = require('node:path');
18
+
19
+ /*
20
+ The capability profiles a shell picks from (shell.json `profile`). A shell cannot pick modules one
21
+ by one: every shell on one profile carries the same native modules, so a fleet of shells has a
22
+ handful of native variants to test, and a shell carries no permission text or store declaration
23
+ for a feature its profile does not have.
24
+
25
+ Each entry lists what the profile adds to the one it `extends`:
26
+ - includes, extensions: other profiles' own entries and shell extensions it carries too
27
+ (Lowdefy Dev carries every profile's modules).
28
+ - capabilities: the device features it gives the app (shell.json `capabilities`).
29
+ - modules: { [package]: nativeModuleName }, the native modules it links. The name is the one the
30
+ module's JavaScript asks for, which scripts/checkReleaseBundle.mjs looks for in a bundle that
31
+ should not carry it; null for a module with no JavaScript of its own in the bundle.
32
+ - plugins: the config plugin entries app.config.ts applies. A profile's entry for a plugin its
33
+ parent also has replaces the parent's.
34
+ - permissions: the iOS Info.plist keys and Android permissions its modules ask for.
35
+ - infoPlist: iOS purpose texts no config plugin writes, as { [Info.plist key]: text }, for a
36
+ module that links a framework App Store Connect asks a purpose for.
37
+ - jsEntries: the feature modules only it uses. A bundle for any other profile resolves them to
38
+ empty modules (metro.config.js).
39
+ - privacyReasons: the required-reason APIs its modules use, for the iOS privacy manifest, as
40
+ { category, reasons } (Apple's NSPrivacyAccessedAPIType and NSPrivacyAccessedAPITypeReasons).
41
+ - dataCollected: the data its features let the app collect, for the App Store privacy label and
42
+ Google Play's data safety form, as { type, purpose }.
43
+
44
+ Every native module the shell package depends on is in some profile. A build leaves out every one
45
+ not in its profile (getExclusions in shellVariant.js), and a release build also leaves out the
46
+ developer features (`dev`'s own entries), whatever its profile.
47
+ */
48
+
49
+ // Lowdefy Dev's developer features: connect, shake menu and dev-tab client (src/dev).
50
+ const DEV_ENTRY = path.join(__dirname, 'src', 'dev', 'index.js');
51
+
52
+ // The device features of profiles and extensions (src/device, src/widgets). A shell that does not
53
+ // carry one resolves it to an empty module, so its native modules are never imported.
54
+ const FIELD_ENTRY = path.join(__dirname, 'src', 'device', 'field', 'index.js');
55
+ const NFC_ENTRY = path.join(__dirname, 'src', 'device', 'nfc', 'index.js');
56
+ const TRACKING_ENTRY = path.join(__dirname, 'src', 'device', 'tracking', 'index.js');
57
+ const CONTACTS_ENTRY = path.join(__dirname, 'src', 'device', 'contacts', 'index.js');
58
+ const CALENDAR_ENTRY = path.join(__dirname, 'src', 'device', 'calendar', 'index.js');
59
+ const WIDGETS_ENTRY = path.join(__dirname, 'src', 'widgets', 'index.js');
60
+
61
+ // The config plugin that adds the App Shortcuts intent to the iOS app (native/withAppShortcuts.js).
62
+ const APP_SHORTCUTS_PLUGIN = './native/withAppShortcuts.js';
63
+
64
+ // One microphone purpose text for every plugin that writes NSMicrophoneUsageDescription: speech
65
+ // (basic) and expo-camera (field, dev). expo-camera writes its own default when no earlier plugin
66
+ // set the key, so it is given the same text and plugin order cannot change what a shell shows.
67
+ const MICROPHONE_PERMISSION =
68
+ '$(PRODUCT_NAME) uses the microphone when you tap the microphone button, to turn what you say into text.';
69
+ // `field` records voice notes too (expo-audio), so its shells claim more for the microphone. Every
70
+ // plugin that writes NSMicrophoneUsageDescription in a field shell is given this text.
71
+ const FIELD_MICROPHONE_PERMISSION =
72
+ '$(PRODUCT_NAME) uses the microphone when you tap the microphone button, to turn what you say into text or to record a voice note.';
73
+ const CAMERA_PERMISSION =
74
+ 'Allow $(PRODUCT_NAME) to use the camera to take photos, scan codes and scan documents.';
75
+ const LOCATION_PERMISSION =
76
+ 'Allow $(PRODUCT_NAME) to use your location to show where you are and record where work is done.';
77
+ const LOCATION_ALWAYS_PERMISSION =
78
+ 'Allow $(PRODUCT_NAME) to use your location in the background to record trips and notice when you arrive at or leave a site.';
79
+ const SPEECH_RECOGNITION_PERMISSION =
80
+ '$(PRODUCT_NAME) turns what you say into text on this device. Your voice is not recorded or sent anywhere.';
81
+ // expo-image links the Photos framework (PhotoLibraryAssetLoader.swift, for `ph://` URIs), so App
82
+ // Store Connect refuses an upload without a photo library purpose (ITMS-90683). No config plugin
83
+ // writes it.
84
+ const PHOTO_LIBRARY_PERMISSION =
85
+ '$(PRODUCT_NAME) shows a photo from your library only when you choose one.';
86
+
87
+ const PROFILES = {
88
+ // What every shell carries: the runtime, native blocks, sign-in, biometric unlock, on-device
89
+ // dictation (expo-speech-recognition, for the Composer and AgentChat mic), push and local
90
+ // notifications, haptics, share out, picking from Photos and Files, read aloud, keep awake, icon
91
+ // quick actions and App Shortcuts, and background fetch. None asks for a permission at install:
92
+ // Photos and Files use the system pickers, and notifications ask on first use.
93
+ basic: {
94
+ capabilities: [
95
+ 'background-fetch',
96
+ 'biometrics',
97
+ 'files',
98
+ 'haptics',
99
+ 'notifications',
100
+ 'photos',
101
+ 'share',
102
+ 'shortcuts',
103
+ 'sign-in',
104
+ 'speech',
105
+ ],
106
+ modules: {
107
+ '@expo/ui': 'ExpoUI',
108
+ 'expo-background-task': 'ExpoBackgroundTask',
109
+ 'expo-clipboard': 'ExpoClipboard',
110
+ 'expo-constants': 'ExponentConstants',
111
+ 'expo-crypto': 'ExpoCrypto',
112
+ 'expo-document-picker': 'ExpoDocumentPicker',
113
+ 'expo-file-system': 'FileSystem',
114
+ 'expo-font': 'ExpoFontLoader',
115
+ 'expo-haptics': 'ExpoHaptics',
116
+ 'expo-image': 'ExpoImage',
117
+ 'expo-image-picker': 'ExponentImagePicker',
118
+ 'expo-keep-awake': 'ExpoKeepAwake',
119
+ 'expo-linking': 'ExpoLinking',
120
+ 'expo-local-authentication': 'ExpoLocalAuthentication',
121
+ 'expo-network': 'ExpoNetwork',
122
+ 'expo-notifications': 'ExpoNotificationCategoriesModule',
123
+ 'expo-quick-actions': 'ExpoQuickActions',
124
+ 'expo-router': null,
125
+ 'expo-secure-store': 'ExpoSecureStore',
126
+ 'expo-sharing': 'ExpoSharing',
127
+ 'expo-speech': 'ExpoSpeech',
128
+ 'expo-speech-recognition': 'ExpoSpeechRecognition',
129
+ 'expo-splash-screen': 'ExpoSplashScreen',
130
+ 'expo-sqlite': 'ExpoSQLite',
131
+ 'expo-task-manager': 'ExpoTaskManager',
132
+ 'expo-updates': 'ExpoUpdates',
133
+ 'expo-web-browser': 'ExpoWebBrowser',
134
+ 'react-native-safe-area-context': 'RNCSafeAreaContext',
135
+ 'react-native-screens': 'RNSModule',
136
+ 'react-native-svg': 'RNSVGSvgViewModule',
137
+ },
138
+ plugins: [
139
+ 'expo-router',
140
+ [
141
+ 'expo-local-authentication',
142
+ { faceIDPermission: 'Allow $(PRODUCT_NAME) to use Face ID to unlock the app.' },
143
+ ],
144
+ [
145
+ 'expo-secure-store',
146
+ { faceIDPermission: 'Allow $(PRODUCT_NAME) to use Face ID to unlock the app.' },
147
+ ],
148
+ [
149
+ 'expo-speech-recognition',
150
+ {
151
+ microphonePermission: MICROPHONE_PERMISSION,
152
+ speechRecognitionPermission: SPEECH_RECOGNITION_PERMISSION,
153
+ },
154
+ ],
155
+ 'expo-splash-screen',
156
+ 'expo-sqlite',
157
+ 'expo-web-browser',
158
+ // Push in production builds (aps-environment), and background notifications so a button with
159
+ // an endpoint runs without opening the app.
160
+ ['expo-notifications', { enableBackgroundRemoteNotifications: true }],
161
+ // Library mode only: the system photo picker needs no permission, and field adds the camera.
162
+ // Its photo library and microphone texts are expo-image's and speech's: `false` would remove
163
+ // the keys App Store Connect (ITMS-90683) and speech need.
164
+ [
165
+ 'expo-image-picker',
166
+ {
167
+ photosPermission: PHOTO_LIBRARY_PERMISSION,
168
+ cameraPermission: false,
169
+ microphonePermission: MICROPHONE_PERMISSION,
170
+ },
171
+ ],
172
+ 'expo-background-task',
173
+ // Share out only; the share extension adds receiving.
174
+ ['expo-sharing', { ios: { enabled: false }, android: { enabled: false } }],
175
+ APP_SHORTCUTS_PLUGIN,
176
+ ],
177
+ permissions: [
178
+ 'NSFaceIDUsageDescription',
179
+ 'android.permission.USE_BIOMETRIC',
180
+ 'NSMicrophoneUsageDescription',
181
+ 'NSSpeechRecognitionUsageDescription',
182
+ 'android.permission.RECORD_AUDIO',
183
+ 'NSPhotoLibraryUsageDescription',
184
+ 'android.permission.POST_NOTIFICATIONS',
185
+ 'android.permission.VIBRATE',
186
+ 'android.permission.RECEIVE_BOOT_COMPLETED',
187
+ 'android.permission.WAKE_LOCK',
188
+ ],
189
+ infoPlist: { NSPhotoLibraryUsageDescription: PHOTO_LIBRARY_PERMISSION },
190
+ jsEntries: [],
191
+ // React Native's own (file timestamps, UserDefaults, boot time), and the free disk space
192
+ // expo-sqlite checks before it writes.
193
+ privacyReasons: [
194
+ { category: 'NSPrivacyAccessedAPICategoryFileTimestamp', reasons: ['C617.1'] },
195
+ { category: 'NSPrivacyAccessedAPICategoryUserDefaults', reasons: ['CA92.1'] },
196
+ { category: 'NSPrivacyAccessedAPICategorySystemBootTime', reasons: ['35F9.1'] },
197
+ { category: 'NSPrivacyAccessedAPICategoryDiskSpace', reasons: ['E174.1'] },
198
+ ],
199
+ dataCollected: [
200
+ { type: 'emailAddress', purpose: 'appFunctionality' },
201
+ { type: 'userId', purpose: 'appFunctionality' },
202
+ // The push token the app's register endpoint stores, and the photos and files a person picks.
203
+ { type: 'deviceId', purpose: 'appFunctionality' },
204
+ { type: 'photos', purpose: 'appFunctionality' },
205
+ { type: 'otherUserContent', purpose: 'appFunctionality' },
206
+ ],
207
+ },
208
+ // Field work: the camera (photos, code and document scanning), location in use, maps, voice
209
+ // notes and screen brightness for scan screens. Its microphone text covers recording, for every
210
+ // plugin that writes it.
211
+ field: {
212
+ extends: 'basic',
213
+ capabilities: ['audio', 'camera', 'document-scanner', 'location', 'maps'],
214
+ modules: {
215
+ 'expo-audio': 'ExpoAudio',
216
+ 'expo-brightness': 'ExpoBrightness',
217
+ 'expo-camera': 'ExpoCamera',
218
+ 'expo-location': 'ExpoLocation',
219
+ 'expo-maps': 'ExpoMaps',
220
+ 'react-native-document-scanner-plugin': 'DocumentScanner',
221
+ },
222
+ // The camera does not add Android's microphone permission: video capture records with the
223
+ // audio permission basic already has for speech.
224
+ plugins: [
225
+ [
226
+ 'expo-speech-recognition',
227
+ {
228
+ microphonePermission: FIELD_MICROPHONE_PERMISSION,
229
+ speechRecognitionPermission: SPEECH_RECOGNITION_PERMISSION,
230
+ },
231
+ ],
232
+ [
233
+ 'expo-camera',
234
+ {
235
+ cameraPermission: CAMERA_PERMISSION,
236
+ microphonePermission: FIELD_MICROPHONE_PERMISSION,
237
+ recordAudioAndroid: false,
238
+ },
239
+ ],
240
+ [
241
+ 'expo-image-picker',
242
+ {
243
+ photosPermission: PHOTO_LIBRARY_PERMISSION,
244
+ cameraPermission: CAMERA_PERMISSION,
245
+ microphonePermission: FIELD_MICROPHONE_PERMISSION,
246
+ },
247
+ ],
248
+ ['expo-audio', { microphonePermission: FIELD_MICROPHONE_PERMISSION }],
249
+ [
250
+ 'expo-location',
251
+ {
252
+ locationWhenInUsePermission: LOCATION_PERMISSION,
253
+ isIosBackgroundLocationEnabled: false,
254
+ isAndroidBackgroundLocationEnabled: false,
255
+ },
256
+ ],
257
+ ['expo-maps', { requestLocationPermission: false }],
258
+ // react-native-document-scanner-plugin's config plugin only writes the camera purpose text,
259
+ // which expo-camera writes above, and requires @expo/config-plugins without depending on it,
260
+ // so the module is linked without it.
261
+ ],
262
+ permissions: [
263
+ 'NSCameraUsageDescription',
264
+ 'android.permission.CAMERA',
265
+ 'NSLocationWhenInUseUsageDescription',
266
+ 'android.permission.ACCESS_COARSE_LOCATION',
267
+ 'android.permission.ACCESS_FINE_LOCATION',
268
+ ],
269
+ jsEntries: [FIELD_ENTRY],
270
+ dataCollected: [
271
+ { type: 'audioData', purpose: 'appFunctionality' },
272
+ { type: 'preciseLocation', purpose: 'appFunctionality' },
273
+ ],
274
+ },
275
+ // `field` and NFC: reading and writing NDEF tags (react-native-nfc-manager).
276
+ 'field-nfc': {
277
+ extends: 'field',
278
+ capabilities: ['nfc'],
279
+ modules: { 'react-native-nfc-manager': 'NfcManager' },
280
+ plugins: [
281
+ [
282
+ 'react-native-nfc-manager',
283
+ { nfcPermission: 'Allow $(PRODUCT_NAME) to read and write NFC tags, such as asset tags.' },
284
+ ],
285
+ ],
286
+ permissions: ['NFCReaderUsageDescription', 'android.permission.NFC'],
287
+ jsEntries: [NFC_ENTRY],
288
+ },
289
+ // `field` and background location and geofences (expo-location in the background, through
290
+ // expo-task-manager). Its session token stays readable while the phone is locked
291
+ // (src/auth/isBackgroundShell.js), so background points and region changes reach the app's
292
+ // endpoints.
293
+ 'field-tracking': {
294
+ extends: 'field',
295
+ capabilities: ['background-location', 'geofencing'],
296
+ plugins: [
297
+ [
298
+ 'expo-location',
299
+ {
300
+ locationWhenInUsePermission: LOCATION_PERMISSION,
301
+ locationAlwaysAndWhenInUsePermission: LOCATION_ALWAYS_PERMISSION,
302
+ isIosBackgroundLocationEnabled: true,
303
+ isAndroidBackgroundLocationEnabled: true,
304
+ isAndroidForegroundServiceEnabled: true,
305
+ },
306
+ ],
307
+ ],
308
+ permissions: [
309
+ 'NSLocationAlwaysAndWhenInUseUsageDescription',
310
+ 'android.permission.ACCESS_BACKGROUND_LOCATION',
311
+ 'android.permission.FOREGROUND_SERVICE',
312
+ 'android.permission.FOREGROUND_SERVICE_LOCATION',
313
+ ],
314
+ jsEntries: [TRACKING_ENTRY],
315
+ },
316
+ // Lowdefy Dev: `field`, NFC, background location, contacts and calendar (so any app's pages can be
317
+ // tried in it), and the developer features, which read the code `lowdefy dev` prints with the
318
+ // camera, open the dev menu on a shake (expo-sensors) and take screenshots for the dev tab
319
+ // (react-native-view-shot). expo-dev-client lets a development build load a local bundle. It
320
+ // carries no share or widget extension: those are app extension targets of an app's own shell.
321
+ dev: {
322
+ extends: 'field',
323
+ includes: ['field-nfc', 'field-tracking'],
324
+ extensions: ['contacts', 'calendar'],
325
+ capabilities: [],
326
+ modules: {
327
+ 'expo-dev-client': null,
328
+ 'expo-sensors': 'ExponentAccelerometer',
329
+ 'react-native-view-shot': 'RNViewShot',
330
+ },
331
+ plugins: [
332
+ [
333
+ 'expo-camera',
334
+ {
335
+ cameraPermission: 'Allow $(PRODUCT_NAME) to scan the code lowdefy dev prints.',
336
+ microphonePermission: FIELD_MICROPHONE_PERMISSION,
337
+ recordAudioAndroid: false,
338
+ },
339
+ ],
340
+ ],
341
+ permissions: ['NSMotionUsageDescription'],
342
+ jsEntries: [DEV_ENTRY],
343
+ },
344
+ // A development build of a developer's own shell (`lowdefy mobile build --profile development`):
345
+ // Lowdefy Dev's features, for apps whose shell plugins Lowdefy Dev does not carry.
346
+ development: {
347
+ extends: 'dev',
348
+ },
349
+ };
350
+
351
+ /*
352
+ The opt-in shell extensions (shell.json `extensions`): rows shaped like a profile's, added on top
353
+ of any profile. Each brings a native target or a permission set a profile should not carry for
354
+ every shell: an app extension with its own bundle id and App Group (share, widgets), or one of
355
+ the permissions review scrutinises most (contacts, calendar). getShellProfile folds them in, and
356
+ the runtime version names them.
357
+ */
358
+ // The widget's source picker. Its options are the app's mobile.widgets sources, set at run time
359
+ // (setConfigurationParameterEnum); the static `default` stands for "the first source" until then.
360
+ const WIDGET_SOURCE_PARAMETER = {
361
+ title: 'Show',
362
+ type: 'enum',
363
+ dynamic: true,
364
+ values: [{ name: 'First', value: 'default' }],
365
+ default: 'default',
366
+ };
367
+
368
+ const EXTENSIONS = {
369
+ // Receiving text, links, images, videos and files from other apps (expo-sharing's share
370
+ // extension on iOS, intent filters on Android). The app opens mobile.share.pageId.
371
+ share: {
372
+ capabilities: ['share-in'],
373
+ plugins: [
374
+ [
375
+ 'expo-sharing',
376
+ {
377
+ ios: {
378
+ enabled: true,
379
+ activationRule: {
380
+ supportsText: true,
381
+ supportsWebUrlWithMaxCount: 1,
382
+ supportsImageWithMaxCount: 10,
383
+ supportsMovieWithMaxCount: 5,
384
+ supportsFileWithMaxCount: 10,
385
+ },
386
+ },
387
+ android: {
388
+ enabled: true,
389
+ singleShareMimeTypes: ['text/*', 'image/*', 'video/*', 'application/*'],
390
+ multipleShareMimeTypes: ['image/*', 'video/*', 'application/*'],
391
+ },
392
+ },
393
+ ],
394
+ ],
395
+ },
396
+ // Home Screen and Lock Screen widgets on iOS (expo-widgets): two fixed layouts fed with data the
397
+ // shell fetches from mobile.widgets endpoints. The widget never holds the session token. Android
398
+ // widgets need their own Compose layouts and come later.
399
+ widgets: {
400
+ capabilities: ['widgets'],
401
+ modules: { 'expo-widgets': 'ExpoWidgets' },
402
+ plugins: [
403
+ [
404
+ 'expo-widgets',
405
+ {
406
+ enableAndroid: false,
407
+ widgets: [
408
+ {
409
+ name: 'LowdefyCount',
410
+ displayName: 'Count',
411
+ description: 'A number from the app, like open tasks.',
412
+ supportedFamilies: [
413
+ 'systemSmall',
414
+ 'accessoryCircular',
415
+ 'accessoryRectangular',
416
+ 'accessoryInline',
417
+ ],
418
+ configuration: {
419
+ title: 'Source',
420
+ parameters: {
421
+ source: WIDGET_SOURCE_PARAMETER,
422
+ },
423
+ },
424
+ },
425
+ {
426
+ name: 'LowdefyList',
427
+ displayName: 'List',
428
+ description: 'A short list from the app, like the next jobs.',
429
+ supportedFamilies: ['systemMedium', 'systemLarge', 'accessoryRectangular'],
430
+ configuration: {
431
+ title: 'Source',
432
+ parameters: {
433
+ source: WIDGET_SOURCE_PARAMETER,
434
+ },
435
+ },
436
+ },
437
+ ],
438
+ },
439
+ ],
440
+ ],
441
+ jsEntries: [WIDGETS_ENTRY],
442
+ },
443
+ // Picking and adding contacts (expo-contacts).
444
+ contacts: {
445
+ capabilities: ['contacts'],
446
+ modules: { 'expo-contacts': 'ExpoContacts' },
447
+ plugins: [
448
+ [
449
+ 'expo-contacts',
450
+ { contactsPermission: 'Allow $(PRODUCT_NAME) to read and add the contacts you choose.' },
451
+ ],
452
+ ],
453
+ permissions: [
454
+ 'NSContactsUsageDescription',
455
+ 'android.permission.READ_CONTACTS',
456
+ 'android.permission.WRITE_CONTACTS',
457
+ ],
458
+ jsEntries: [CONTACTS_ENTRY],
459
+ dataCollected: [{ type: 'contacts', purpose: 'appFunctionality' }],
460
+ },
461
+ // Reading and adding calendar events (expo-calendar).
462
+ calendar: {
463
+ capabilities: ['calendar'],
464
+ modules: { 'expo-calendar': 'ExpoCalendar' },
465
+ plugins: [
466
+ [
467
+ 'expo-calendar',
468
+ {
469
+ calendarPermission:
470
+ 'Allow $(PRODUCT_NAME) to read your calendar and add events you choose.',
471
+ remindersPermission: false,
472
+ },
473
+ ],
474
+ ],
475
+ permissions: [
476
+ 'NSCalendarsFullAccessUsageDescription',
477
+ 'NSCalendarsUsageDescription',
478
+ 'android.permission.READ_CALENDAR',
479
+ 'android.permission.WRITE_CALENDAR',
480
+ ],
481
+ jsEntries: [CALENDAR_ENTRY],
482
+ dataCollected: [{ type: 'otherUserContent', purpose: 'appFunctionality' }],
483
+ },
484
+ };
485
+
486
+ // The profiles a store or testing build of an app's shell picks from.
487
+ const RELEASE_PROFILES = ['basic', 'field', 'field-nfc', 'field-tracking'];
488
+
489
+ function pluginName(plugin) {
490
+ return Array.isArray(plugin) ? plugin[0] : plugin;
491
+ }
492
+
493
+ function unique(values) {
494
+ return [...new Set(values)];
495
+ }
496
+
497
+ // Merges two resolved profiles, or a profile entry onto its resolved parent: lists are joined
498
+ // without duplicates, and `added`'s modules, plugins and Info.plist texts replace `base`'s of the
499
+ // same name.
500
+ function mergeProfiles(base, added) {
501
+ const plugins = [...base.plugins];
502
+ (added.plugins ?? []).forEach((plugin) => {
503
+ const index = plugins.findIndex((existing) => pluginName(existing) === pluginName(plugin));
504
+ if (index === -1) {
505
+ plugins.push(plugin);
506
+ } else {
507
+ plugins[index] = plugin;
508
+ }
509
+ });
510
+ const privacyReasons = base.privacyReasons.map((reason) => ({ ...reason }));
511
+ (added.privacyReasons ?? []).forEach(({ category, reasons }) => {
512
+ const existing = privacyReasons.find((reason) => reason.category === category);
513
+ if (existing) {
514
+ existing.reasons = unique([...existing.reasons, ...reasons]);
515
+ } else {
516
+ privacyReasons.push({ category, reasons: [...reasons] });
517
+ }
518
+ });
519
+ const dataCollected = [...base.dataCollected];
520
+ (added.dataCollected ?? []).forEach((data) => {
521
+ if (!dataCollected.some(({ type }) => type === data.type)) {
522
+ dataCollected.push(data);
523
+ }
524
+ });
525
+ return {
526
+ name: added.name ?? base.name,
527
+ capabilities: unique([...base.capabilities, ...(added.capabilities ?? [])]),
528
+ modules: { ...base.modules, ...added.modules },
529
+ plugins,
530
+ permissions: unique([...base.permissions, ...(added.permissions ?? [])]),
531
+ infoPlist: { ...base.infoPlist, ...added.infoPlist },
532
+ jsEntries: unique([...base.jsEntries, ...(added.jsEntries ?? [])]),
533
+ privacyReasons,
534
+ dataCollected,
535
+ };
536
+ }
537
+
538
+ const EMPTY_PROFILE = {
539
+ capabilities: [],
540
+ modules: {},
541
+ plugins: [],
542
+ permissions: [],
543
+ infoPlist: {},
544
+ jsEntries: [],
545
+ privacyReasons: [],
546
+ dataCollected: [],
547
+ };
548
+
549
+ /**
550
+ * A capability profile with the shell's extensions folded in (EXTENSIONS). The profile keeps its
551
+ * name.
552
+ *
553
+ * @param {Object} profile A resolved profile.
554
+ * @param {string[]} [extensions] shell.json `extensions`.
555
+ */
556
+ function applyExtensions(profile, extensions = []) {
557
+ return extensions.reduce((merged, name) => {
558
+ if (!Object.prototype.hasOwnProperty.call(EXTENSIONS, name)) {
559
+ throw new Error(
560
+ `Shell extension ${JSON.stringify(name)} does not exist. Use one of ${Object.keys(
561
+ EXTENSIONS
562
+ ).join(', ')}.`
563
+ );
564
+ }
565
+ return mergeProfiles(merged, { ...EXTENSIONS[name], name: profile.name });
566
+ }, profile);
567
+ }
568
+
569
+ /**
570
+ * Everything a capability profile carries, with the profiles it extends folded in.
571
+ *
572
+ * @param {string} name The profile's name (shell.json `profile`).
573
+ * @returns {{ name, capabilities, modules, plugins, permissions, infoPlist, jsEntries,
574
+ * privacyReasons, dataCollected }}
575
+ */
576
+ function resolveProfile(name) {
577
+ if (!Object.prototype.hasOwnProperty.call(PROFILES, name)) {
578
+ throw new Error(
579
+ `Shell profile ${JSON.stringify(name)} does not exist. Use one of ${Object.keys(
580
+ PROFILES
581
+ ).join(', ')}.`
582
+ );
583
+ }
584
+ const entry = PROFILES[name];
585
+ const parent = entry.extends ? resolveProfile(entry.extends) : EMPTY_PROFILE;
586
+ const included = (entry.includes ?? []).reduce(
587
+ (merged, include) => mergeProfiles(merged, { ...PROFILES[include], name }),
588
+ parent
589
+ );
590
+ return mergeProfiles(applyExtensions(included, entry.extensions), { ...entry, name });
591
+ }
592
+
593
+ module.exports = {
594
+ applyExtensions,
595
+ DEV_ENTRY,
596
+ EXTENSIONS,
597
+ FIELD_MICROPHONE_PERMISSION,
598
+ MICROPHONE_PERMISSION,
599
+ PHOTO_LIBRARY_PERMISSION,
600
+ PROFILES,
601
+ RELEASE_PROFILES,
602
+ mergeProfiles,
603
+ pluginName,
604
+ resolveProfile,
605
+ SPEECH_RECOGNITION_PERMISSION,
606
+ WIDGETS_ENTRY,
607
+ };
@@ -0,0 +1,36 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+
20
+ const checkShellConfig = require('./checkShellConfig.js');
21
+
22
+ // The shell's config comes from shell.json, written for each shell at build: LOWDEFY_SHELL_CONFIG
23
+ // is its path. Without it the shell is Lowdefy Dev (shell.dev.json), which opens `lowdefy dev`.
24
+ // app.config.ts, metro.config.js and scripts/checkReleaseBundle.mjs read it here.
25
+ function readShellConfig(env = process.env) {
26
+ const configPath = env.LOWDEFY_SHELL_CONFIG
27
+ ? path.resolve(env.LOWDEFY_SHELL_CONFIG)
28
+ : path.join(__dirname, 'shell.dev.json');
29
+ const shell = checkShellConfig({
30
+ configPath,
31
+ shell: JSON.parse(fs.readFileSync(configPath, 'utf8')),
32
+ });
33
+ return { configPath, shell };
34
+ }
35
+
36
+ module.exports = readShellConfig;