@lowdefy/shell 3.23.2 → 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.
- package/LICENSE +183 -183
- package/README.md +364 -2
- package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
- package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
- package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
- package/app/+native-intent.js +39 -0
- package/app/_layout.js +36 -0
- package/app.config.ts +183 -0
- package/appLinks.js +65 -0
- package/applyShellPlugins.js +43 -0
- package/authCallbackIntentFilters.js +46 -0
- package/babel.config.js +25 -0
- package/checkShellConfig.js +172 -0
- package/checkShellPlugins.js +74 -0
- package/getPrivacyManifest.js +39 -0
- package/metro.config.js +73 -0
- package/native/LowdefyAppShortcuts.swift.template +84 -0
- package/native/withAppShortcuts.js +60 -0
- package/native-fingerprint.json +16 -0
- package/package.json +120 -40
- package/profiles.js +607 -0
- package/readShellConfig.js +36 -0
- package/resolveShellPlugins.js +109 -0
- package/runtimeVersion.js +112 -0
- package/scripts/checkReleaseBundle.mjs +358 -0
- package/scripts/createDayjsLocales.mjs +67 -0
- package/scripts/createShellAssets.mjs +54 -0
- package/scripts/createShellVersion.mjs +83 -0
- package/scripts/createTypeManifest.mjs +205 -0
- package/shell.dev.json +11 -0
- package/shellVariant.js +126 -0
- package/src/ShellContext.js +32 -0
- package/src/actions/CopyToClipboard.js +29 -0
- package/src/actions/CopyToClipboard.test.js +33 -0
- package/src/actions/GeolocationCurrentPosition.js +61 -0
- package/src/actions/GeolocationCurrentPosition.test.js +79 -0
- package/src/actions/GetLocalStorage.js +36 -0
- package/src/actions/RemoveLocalStorage.js +31 -0
- package/src/actions/ScrollTo.js +66 -0
- package/src/actions/ScrollTo.test.js +83 -0
- package/src/actions/SetFocus.js +33 -0
- package/src/actions/SetFocus.test.js +49 -0
- package/src/actions/SetLocalStorage.js +34 -0
- package/src/actions/localStorage.test.js +68 -0
- package/src/actions/measureInScrollView.js +36 -0
- package/src/actions/nativeActions.js +41 -0
- package/src/auth/AuthButton.js +44 -0
- package/src/auth/AuthGate.js +57 -0
- package/src/auth/AuthLink.js +43 -0
- package/src/auth/LockScreen.js +67 -0
- package/src/auth/LockScreen.test.js +37 -0
- package/src/auth/SignInScreen.js +268 -0
- package/src/auth/SignInScreen.test.js +187 -0
- package/src/auth/createAppAuth.js +325 -0
- package/src/auth/createAppAuth.test.js +424 -0
- package/src/auth/createAuthApi.js +115 -0
- package/src/auth/createAuthApi.test.js +138 -0
- package/src/auth/createCoreAuth.js +96 -0
- package/src/auth/createCoreAuth.test.js +188 -0
- package/src/auth/createDeviceKey.js +62 -0
- package/src/auth/createDeviceKey.test.js +54 -0
- package/src/auth/createSessionStores.js +45 -0
- package/src/auth/createTokenStore.js +103 -0
- package/src/auth/createTokenStore.test.js +116 -0
- package/src/auth/getAuthSettings.js +24 -0
- package/src/auth/getAuthView.js +39 -0
- package/src/auth/getAuthView.test.js +52 -0
- package/src/auth/getSheetReturnUrl.js +35 -0
- package/src/auth/getTokenStoreOptions.js +46 -0
- package/src/auth/isBackgroundShell.js +26 -0
- package/src/auth/isSheetReturnLink.js +35 -0
- package/src/auth/parseQuery.js +39 -0
- package/src/auth/purgeAppData.js +80 -0
- package/src/auth/purgeAppData.test.js +104 -0
- package/src/auth/shouldLock.js +26 -0
- package/src/auth/signInWithSheet.js +97 -0
- package/src/auth/signInWithSheet.test.js +205 -0
- package/src/auth/styles.js +78 -0
- package/src/auth/unregisterPushToken.js +62 -0
- package/src/auth/unregisterPushToken.test.js +67 -0
- package/src/auth/usesBiometricUnlock.js +23 -0
- package/src/auth/watchSession.js +47 -0
- package/src/auth/withoutCookies.js +26 -0
- package/src/cache/createSqliteCacheStore.js +113 -0
- package/src/cache/createSqliteCacheStore.test.js +93 -0
- package/src/cache/getCacheKey.js +25 -0
- package/src/cache/getLastCacheKeyName.js +22 -0
- package/src/components/AppProvider.js +118 -0
- package/src/components/Frame.js +116 -0
- package/src/components/Frame.test.js +101 -0
- package/src/components/LoadingScreen.js +35 -0
- package/src/components/MenuButton.js +121 -0
- package/src/components/MenuButton.test.js +74 -0
- package/src/components/MessageScreen.js +51 -0
- package/src/components/NavigationTheme.js +33 -0
- package/src/components/NoConnectionBanner.js +51 -0
- package/src/components/NoConnectionBanner.test.js +73 -0
- package/src/components/OtherPageScreen.js +95 -0
- package/src/components/OtherPageScreen.test.js +74 -0
- package/src/components/PageScreen.js +254 -0
- package/src/components/PageScreen.test.js +492 -0
- package/src/components/RouteErrorScreen.js +47 -0
- package/src/components/ShellRoot.js +34 -0
- package/src/components/ShellRoot.test.js +62 -0
- package/src/components/ShellThemeProvider.js +45 -0
- package/src/components/SwitchToTab.js +36 -0
- package/src/components/SwitchToTab.test.js +63 -0
- package/src/components/TabIconImages.js +72 -0
- package/src/components/UpdateScreen.js +40 -0
- package/src/components/getNavigationTheme.js +44 -0
- package/src/components/getNavigationTheme.test.js +41 -0
- package/src/components/styles.js +50 -0
- package/src/components/useSurfaceTokens.js +47 -0
- package/src/config/getAppApiBase.js +34 -0
- package/src/config/getShellConfig.js +37 -0
- package/src/config/getShellConfig.test.js +80 -0
- package/src/createShellApp.js +126 -0
- package/src/dev/DevShell.js +239 -0
- package/src/dev/DevShell.test.js +233 -0
- package/src/dev/connect/ConfirmSheet.js +71 -0
- package/src/dev/connect/ConnectScreen.js +117 -0
- package/src/dev/connect/QrScanner.js +89 -0
- package/src/dev/connection/createDevShellConfig.js +47 -0
- package/src/dev/connection/createDevTransport.js +51 -0
- package/src/dev/connection/createDevTransport.test.js +87 -0
- package/src/dev/connection/createServerList.js +48 -0
- package/src/dev/connection/isLoopbackHost.js +25 -0
- package/src/dev/connection/pairedShellApp.test.js +104 -0
- package/src/dev/connection/parseDevLink.js +66 -0
- package/src/dev/connection/parseDevLink.test.js +97 -0
- package/src/dev/connection/parseQuery.js +33 -0
- package/src/dev/connection/parseServerInput.js +40 -0
- package/src/dev/connection/parseServerUrl.js +45 -0
- package/src/dev/devStyles.js +97 -0
- package/src/dev/index.js +21 -0
- package/src/dev/inject/createDeviceInjections.js +188 -0
- package/src/dev/inject/createDeviceInjections.test.js +224 -0
- package/src/dev/inject/createOfflineFetch.js +35 -0
- package/src/dev/inject/createOfflineFetch.test.js +68 -0
- package/src/dev/inject/injectPush.js +60 -0
- package/src/dev/inject/injectShare.js +55 -0
- package/src/dev/inject/withInjectedDevice.js +98 -0
- package/src/dev/menu/BlockGallery.js +33 -0
- package/src/dev/menu/DevMenu.js +121 -0
- package/src/dev/menu/createLogBuffer.js +83 -0
- package/src/dev/menu/watchShake.js +52 -0
- package/src/dev/menu/watchShake.test.js +61 -0
- package/src/dev/recorder/createNativeRecorder.js +204 -0
- package/src/dev/recorder/createNativeRecorder.test.js +307 -0
- package/src/dev/redirectDevLink.js +30 -0
- package/src/dev/redirectDevLink.test.js +49 -0
- package/src/dev/steps/createStepRunner.js +184 -0
- package/src/dev/steps/createStepRunner.test.js +262 -0
- package/src/dev/steps/findBlock.js +78 -0
- package/src/dev/steps/isContextSettled.js +37 -0
- package/src/dev/steps/waitForSettle.js +39 -0
- package/src/dev/steps/waitUntil.js +40 -0
- package/src/dev/tab/DevTabClient.js +97 -0
- package/src/dev/tab/answerEval.js +39 -0
- package/src/dev/tab/answerInspect.js +45 -0
- package/src/dev/tab/createDevTabClient.js +272 -0
- package/src/dev/tab/createDevTabClient.test.js +317 -0
- package/src/dev/tab/describeTabError.js +35 -0
- package/src/dev/tab/devClientMarker.js +22 -0
- package/src/dev/tab/devClientMarker.test.js +23 -0
- package/src/dev/tab/findPageContext.js +43 -0
- package/src/dev/tab/getShownPage.js +42 -0
- package/src/dev/tab/readEventStream.js +68 -0
- package/src/dev/tab/reloadApp.js +42 -0
- package/src/dev/tab/reloadApp.test.js +115 -0
- package/src/device/DeviceHosts.js +32 -0
- package/src/device/basic/createFilesFeature.js +41 -0
- package/src/device/basic/createHapticsFeature.js +46 -0
- package/src/device/basic/createKeepAwakeFeature.js +37 -0
- package/src/device/basic/createPhotosFeature.js +40 -0
- package/src/device/basic/createRequestQueueFeature.js +37 -0
- package/src/device/basic/createShareFeature.js +63 -0
- package/src/device/basic/createShareFeature.test.js +47 -0
- package/src/device/basic/createSpeechFeature.js +43 -0
- package/src/device/basic/createUploadFeature.js +41 -0
- package/src/device/basic/toImagePickerOptions.js +41 -0
- package/src/device/basic/toPickedFile.js +54 -0
- package/src/device/calendar/index.js +68 -0
- package/src/device/contacts/index.js +67 -0
- package/src/device/createDevice.js +65 -0
- package/src/device/device.js +27 -0
- package/src/device/field/MapView.js +93 -0
- package/src/device/field/ScanCodeSheet.js +86 -0
- package/src/device/field/ScannerView.js +52 -0
- package/src/device/field/createAudioFeature.js +70 -0
- package/src/device/field/createBrightnessFeature.js +40 -0
- package/src/device/field/createCameraFeature.js +56 -0
- package/src/device/field/createDocumentScannerFeature.js +51 -0
- package/src/device/field/createLocationFeature.js +39 -0
- package/src/device/field/createScanRequests.js +55 -0
- package/src/device/field/index.js +39 -0
- package/src/device/field/scanRequests.js +22 -0
- package/src/device/injected.js +60 -0
- package/src/device/injected.test.js +39 -0
- package/src/device/nfc/index.js +89 -0
- package/src/device/nfc/toNfcRecord.js +44 -0
- package/src/device/nfc/toNfcRecord.test.js +48 -0
- package/src/device/tracking/createBacklog.js +63 -0
- package/src/device/tracking/createBacklog.test.js +47 -0
- package/src/device/tracking/index.js +211 -0
- package/src/device/tracking/index.test.js +110 -0
- package/src/host/createDeviceStore.js +32 -0
- package/src/host/createMemoryStore.js +32 -0
- package/src/host/createNetworkStatus.js +94 -0
- package/src/host/createNetworkStatus.test.js +94 -0
- package/src/host/createRouterAdapter.js +33 -0
- package/src/host/createShellFetch.js +62 -0
- package/src/host/createShellFetch.test.js +165 -0
- package/src/host/createShellGlobals.js +62 -0
- package/src/host/createShellHost.js +83 -0
- package/src/host/createShellHost.test.js +36 -0
- package/src/host/getOrigin.js +31 -0
- package/src/host/isNetworkError.js +24 -0
- package/src/host/isOnline.js +22 -0
- package/src/host/networkStatus.js +24 -0
- package/src/host/readServerError.js +45 -0
- package/src/host/readServerError.test.js +85 -0
- package/src/host/reportShellError.js +47 -0
- package/src/host/reportShellError.test.js +79 -0
- package/src/host/withNetworkErrors.js +38 -0
- package/src/host/withNetworkErrors.test.js +61 -0
- package/src/host/withShellHeader.js +25 -0
- package/src/host/withShellHeader.test.js +28 -0
- package/src/js/evaluateJsEntries.js +43 -0
- package/src/js/evaluateJsEntries.test.js +48 -0
- package/src/locale/createShellLocale.js +112 -0
- package/src/locale/createShellLocale.test.js +74 -0
- package/src/locale/dayjsLocales.js +165 -0
- package/src/locale/getDeviceLocales.js +23 -0
- package/src/locale/loadDayjsLocale.js +32 -0
- package/src/locale/resolveActiveLocale.js +46 -0
- package/src/locale/resolveActiveLocale.test.js +40 -0
- package/src/native/backgroundFetch.js +97 -0
- package/src/native/callEndpoint.js +63 -0
- package/src/native/createAppEventTrigger.js +70 -0
- package/src/native/currentApp.js +49 -0
- package/src/native/getPagePath.js +32 -0
- package/src/native/openPath.js +55 -0
- package/src/native/openPath.test.js +39 -0
- package/src/native/parseShellLink.js +38 -0
- package/src/native/parseShellLink.test.js +29 -0
- package/src/native/startAppServices.js +94 -0
- package/src/native/startAppServices.test.js +97 -0
- package/src/native/startShareIn.js +59 -0
- package/src/native/startShortcuts.js +88 -0
- package/src/native/startShortcuts.test.js +59 -0
- package/src/native/toSharePayload.js +47 -0
- package/src/native/toSharePayload.test.js +47 -0
- package/src/native/whenAppReady.js +48 -0
- package/src/navigation/SlotContext.js +24 -0
- package/src/navigation/frameSlots.js +27 -0
- package/src/navigation/getFrame.js +30 -0
- package/src/navigation/getInitialScreenOptions.js +63 -0
- package/src/navigation/getInitialScreenOptions.test.js +66 -0
- package/src/navigation/getPresentation.js +28 -0
- package/src/navigation/getSlotRootPath.js +27 -0
- package/src/navigation/getStackOptions.js +185 -0
- package/src/navigation/getTabHref.js +45 -0
- package/src/navigation/getTabIcons.js +43 -0
- package/src/navigation/isPageUnderHeader.js +46 -0
- package/src/navigation/navigation.test.js +342 -0
- package/src/navigation/toAppLinkPath.js +43 -0
- package/src/navigation/toAppLinkPath.test.js +53 -0
- package/src/navigation/toRouterHref.js +29 -0
- package/src/notifications/createNotificationsFeature.js +61 -0
- package/src/notifications/getCategoryAction.js +31 -0
- package/src/notifications/handleNotificationResponse.js +82 -0
- package/src/notifications/registerPushToken.js +71 -0
- package/src/notifications/startNotifications.js +114 -0
- package/src/notifications/toCategoryActions.js +42 -0
- package/src/notifications/toNotificationEvent.js +36 -0
- package/src/notifications/toScheduleRequest.js +62 -0
- package/src/plugins/appTypes.js +28 -0
- package/src/plugins/prefixTypes.js +28 -0
- package/src/plugins/shellPlugins.json +15 -0
- package/src/plugins/typeManifest.js +789 -0
- package/src/plugins/types.js +64 -0
- package/src/plugins/types.test.js +173 -0
- package/src/polyfills/installGetRandomValues.js +28 -0
- package/src/queue/createRequestQueue.js +198 -0
- package/src/queue/createRequestQueue.test.js +215 -0
- package/src/queue/createSqliteQueueStore.js +121 -0
- package/src/queue/summarizeQueue.js +39 -0
- package/src/session/createAppSession.js +323 -0
- package/src/session/createAppSession.test.js +391 -0
- package/src/session/getPageView.js +59 -0
- package/src/session/getPageView.test.js +137 -0
- package/src/sync/encodePagePath.js +27 -0
- package/src/sync/fetchPage.js +72 -0
- package/src/sync/fetchPage.test.js +84 -0
- package/src/sync/fetchRoot.js +43 -0
- package/src/sync/fetchUser.js +33 -0
- package/src/sync/runLimited.js +36 -0
- package/src/sync/toRootConfig.js +35 -0
- package/src/test/createFakeNetwork.js +40 -0
- package/src/test/createFakeSecureStore.js +40 -0
- package/src/test/createFakeServer.js +98 -0
- package/src/test/createMemoryCacheStore.js +59 -0
- package/src/test/createNetworkError.js +24 -0
- package/src/test/expoImage.js +42 -0
- package/src/test/expoNotifications.js +40 -0
- package/src/test/expoQuickActions.js +22 -0
- package/src/test/expoSpeechRecognition.js +31 -0
- package/src/test/expoTaskManager.js +25 -0
- package/src/test/expoUi.js +55 -0
- package/src/test/expoUiBottomSheet.js +64 -0
- package/src/test/expoUiDateTimePicker.js +26 -0
- package/src/test/expoUiMenu.js +46 -0
- package/src/test/expoUiSegmentedControl.js +47 -0
- package/src/test/expoUiSwiftUi.js +62 -0
- package/src/test/expoUiSwiftUiModifiers.js +23 -0
- package/src/test/expoWebBrowser.js +23 -0
- package/src/test/fixtures.js +98 -0
- package/src/test/nativeModule.js +33 -0
- package/src/test/renderPageScreen.js +109 -0
- package/src/types/checkTypeSet.js +39 -0
- package/src/types/collectPageTypes.js +49 -0
- package/src/types/collectPageTypes.test.js +90 -0
- package/src/types/findMissingPageTypes.js +40 -0
- package/src/version/compareVersions.js +38 -0
- package/src/version/isShellTooOld.js +30 -0
- package/src/version/shellVersion.js +21 -0
- package/src/version/shellVersion.test.js +32 -0
- package/src/widgets/fetchWidgetData.js +59 -0
- package/src/widgets/index.js +156 -0
- package/src/widgets/refreshWidgets.js +31 -0
- package/withExcludedNativeModules.js +93 -0
- package/dist/location.js +0 -33
- package/dist/public/apple-touch-icon.png +0 -0
- package/dist/public/icon-32.png +0 -0
- package/dist/public/icon-512.png +0 -0
- package/dist/public/icon.svg +0 -17
- package/dist/public/logo-dark-theme.png +0 -0
- package/dist/public/logo-light-theme.png +0 -0
- package/dist/public/logo-square-dark-theme.png +0 -0
- package/dist/public/logo-square-light-theme.png +0 -0
- package/dist/public/manifest.webmanifest +0 -16
- package/dist/shell/246.93432ff68186aae6c87b.js +0 -2
- package/dist/shell/246.93432ff68186aae6c87b.js.LICENSE.txt +0 -8
- package/dist/shell/454.b597ca21366afda000af.js +0 -2
- package/dist/shell/454.b597ca21366afda000af.js.LICENSE.txt +0 -14
- package/dist/shell/484.d645e6c09079f45ce044.js +0 -1
- package/dist/shell/706.a66931e02a549fd269bd.js +0 -2
- package/dist/shell/706.a66931e02a549fd269bd.js.LICENSE.txt +0 -8
- package/dist/shell/9.78be8c1b6c3986f404fc.js +0 -2
- package/dist/shell/9.78be8c1b6c3986f404fc.js.LICENSE.txt +0 -45
- package/dist/shell/922.43b7a3ebe22ef05b3049.js +0 -2
- package/dist/shell/922.43b7a3ebe22ef05b3049.js.LICENSE.txt +0 -14
- package/dist/shell/index.html +0 -57
- package/dist/shell/main.bde87d1433f0625faed6.js +0 -1
- package/dist/shell/runtime.1ead93159ba56c05b969.js +0 -1
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
/*
|
|
18
|
+
A shell's plugins (shell.json `plugins`): npm packages that add native code beyond the shell's
|
|
19
|
+
capability profile (profiles.js). Each is a Lowdefy shell plugin, which exports:
|
|
20
|
+
|
|
21
|
+
- `./shell` (CommonJS): an entry shaped like one row of the profiles table, which app.config.ts
|
|
22
|
+
folds into the shell's resolved profile (applyShellPlugins.js):
|
|
23
|
+
- capabilities: the device features it gives the app.
|
|
24
|
+
- modules: { [package]: nativeModuleName }, the native modules it links. Never one
|
|
25
|
+
@lowdefy/shell carries itself (checkShellPlugins.js).
|
|
26
|
+
- plugins: the Expo config plugin entries it applies, or a function of the plugin's `options`
|
|
27
|
+
(shell.json `plugins[].options`) that returns them.
|
|
28
|
+
- permissions: the iOS Info.plist keys and Android permissions its modules ask for.
|
|
29
|
+
- privacyReasons: the required-reason APIs its modules use, as { category, reasons }.
|
|
30
|
+
- dataCollected: the data it lets the app collect, as { type, purpose }.
|
|
31
|
+
- `./native` (optional): its native types, as named exports `blocks` and `actions`, each
|
|
32
|
+
{ [typeName]: implementation }. `lowdefy mobile config` writes src/plugins/appTypes.js in the
|
|
33
|
+
copied shell to import them, with the plugin's `typePrefix` applied (prefixTypes.js), and
|
|
34
|
+
src/plugins/types.js registers them after the built-in types.
|
|
35
|
+
- `./types` (optional): its type names as other plugins give them, with its native ones under
|
|
36
|
+
`native: { blocks, actions, blockMetas, icons }`. scripts/createTypeManifest.mjs adds
|
|
37
|
+
`native.blocks` and `native.actions` to the type manifest.
|
|
38
|
+
|
|
39
|
+
Its native modules and `expo` are optional peerDependencies, so the web server's install leaves
|
|
40
|
+
them out and `lowdefy mobile config` adds them to the shell project.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
function isObject(value) {
|
|
44
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const LIST_KEYS = ['capabilities', 'permissions', 'privacyReasons', 'dataCollected'];
|
|
48
|
+
|
|
49
|
+
function loadEntry({ name, require }) {
|
|
50
|
+
try {
|
|
51
|
+
return require(`${name}/shell`);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Shell plugin "${name}" could not be loaded from "${name}/shell". A shell plugin exports "./shell", the native modules, config plugins, permissions, privacy reasons and data it brings. ${error.message}`,
|
|
55
|
+
{ cause: error }
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function checkEntry({ entry, name }) {
|
|
61
|
+
const where = `Shell plugin "${name}" "./shell"`;
|
|
62
|
+
if (!isObject(entry)) {
|
|
63
|
+
throw new Error(`${where} should export an object. Received ${JSON.stringify(entry)}.`);
|
|
64
|
+
}
|
|
65
|
+
LIST_KEYS.forEach((key) => {
|
|
66
|
+
if (entry[key] !== undefined && !Array.isArray(entry[key])) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`${where} "${key}" should be an array. Received ${JSON.stringify(entry[key])}.`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (entry.modules !== undefined && !isObject(entry.modules)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`${where} "modules" should be { [package]: nativeModuleName }. Received ${JSON.stringify(
|
|
75
|
+
entry.modules
|
|
76
|
+
)}.`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (!Array.isArray(entry.plugins)) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`${where} "plugins" should be an array of config plugin entries, or a function of the plugin's options that returns one. Received ${JSON.stringify(
|
|
82
|
+
entry.plugins
|
|
83
|
+
)}.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Loads the `./shell` entry of each plugin a shell lists, with a `plugins` function called with the
|
|
90
|
+
* plugin's options.
|
|
91
|
+
*
|
|
92
|
+
* @param {Object} params
|
|
93
|
+
* @param {Object} params.shell The shell config (shell.json).
|
|
94
|
+
* @param {Function} params.require Resolves the plugin packages: the shell project's own, so
|
|
95
|
+
* `lowdefy mobile` resolves them from the copied project.
|
|
96
|
+
* @returns {{ name: string, version: string, typePrefix?: string, options?: Object, entry: Object }[]}
|
|
97
|
+
*/
|
|
98
|
+
function resolveShellPlugins({ shell, require }) {
|
|
99
|
+
return (shell.plugins ?? []).map(({ name, version, typePrefix, options }) => {
|
|
100
|
+
const loaded = loadEntry({ name, require });
|
|
101
|
+
const plugins =
|
|
102
|
+
typeof loaded?.plugins === 'function' ? loaded.plugins(options ?? {}) : loaded?.plugins ?? [];
|
|
103
|
+
const entry = isObject(loaded) ? { ...loaded, plugins } : loaded;
|
|
104
|
+
checkEntry({ entry, name });
|
|
105
|
+
return { name, version, typePrefix, options, entry };
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = resolveShellPlugins;
|
|
@@ -0,0 +1,112 @@
|
|
|
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 crypto = require('node:crypto');
|
|
18
|
+
|
|
19
|
+
function isPositiveInteger(value) {
|
|
20
|
+
return Number.isInteger(value) && value > 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isObject(value) {
|
|
24
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// JSON with every object's keys sorted, at every depth, so options that differ only in key order
|
|
28
|
+
// give the same text.
|
|
29
|
+
function canonicalJson(value) {
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
32
|
+
}
|
|
33
|
+
if (isObject(value)) {
|
|
34
|
+
const entries = Object.keys(value)
|
|
35
|
+
.sort()
|
|
36
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`);
|
|
37
|
+
return `{${entries.join(',')}}`;
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// A short hash of the shell plugins a shell carries, the same whatever order they are listed in.
|
|
43
|
+
// Each plugin is keyed on name@version and its options: config plugins bake the options into the
|
|
44
|
+
// native project, so an option change is a native change. No options and {} hash the same.
|
|
45
|
+
function hashPlugins(plugins) {
|
|
46
|
+
const keys = plugins.map((plugin) => {
|
|
47
|
+
if (typeof plugin?.name !== 'string' || typeof plugin?.version !== 'string') {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Each shell plugin should be { name, version } with string values. Received ${JSON.stringify(
|
|
50
|
+
plugin
|
|
51
|
+
)}.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (plugin.options !== undefined && !isObject(plugin.options)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Shell plugin "${plugin.name}" options should be an object. Received ${JSON.stringify(
|
|
57
|
+
plugin.options
|
|
58
|
+
)}.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return `${plugin.name}@${plugin.version} ${canonicalJson(plugin.options ?? {})}`;
|
|
62
|
+
});
|
|
63
|
+
return crypto.createHash('sha256').update(keys.sort().join('\n')).digest('hex').slice(0, 8);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The runtime version of a shell build: `<nativeVersion>-<profile>`, `+<extension>` for each
|
|
68
|
+
* extension it carries (in a fixed order), and `-<plugins hash>` when it carries shell plugins. An over-the-air update reaches only binaries with the same runtime
|
|
69
|
+
* version, so it changes only with the shell's native code (`nativeVersion` in package.json, bumped
|
|
70
|
+
* on purpose and kept honest by scripts/checkNativeVersion.mjs), its capability profile and its
|
|
71
|
+
* plugins. Shell identity (name, ids, scheme, icon, colour, URLs) is not part of it, so every shell
|
|
72
|
+
* on one nativeVersion and profile shares a runtime version.
|
|
73
|
+
*
|
|
74
|
+
* Pure, with no Expo import, so `lowdefy mobile` computes the same value app.config.ts sets from the copied
|
|
75
|
+
* shell's runtimeVersion.js and package.json `nativeVersion`.
|
|
76
|
+
*
|
|
77
|
+
* @param {Object} params
|
|
78
|
+
* @param {number} params.nativeVersion The shell package's nativeVersion.
|
|
79
|
+
* @param {string} params.profile The shell's capability profile.
|
|
80
|
+
* @param {{ name: string, version: string, options?: Object }[]} [params.plugins] The shell plugins
|
|
81
|
+
* it carries (shell.json `plugins`).
|
|
82
|
+
* @param {string[]} [params.extensions] The shell extensions it carries (shell.json `extensions`).
|
|
83
|
+
* @returns {string}
|
|
84
|
+
*/
|
|
85
|
+
function getRuntimeVersion({ nativeVersion, profile, plugins = [], extensions = [] }) {
|
|
86
|
+
if (!isPositiveInteger(nativeVersion)) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`nativeVersion should be a positive integer. Received ${JSON.stringify(nativeVersion)}.`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (typeof profile !== 'string' || profile.length === 0) {
|
|
92
|
+
throw new Error(`profile should be a string. Received ${JSON.stringify(profile)}.`);
|
|
93
|
+
}
|
|
94
|
+
if (!Array.isArray(plugins)) {
|
|
95
|
+
throw new Error(`plugins should be an array. Received ${JSON.stringify(plugins)}.`);
|
|
96
|
+
}
|
|
97
|
+
if (!Array.isArray(extensions) || !extensions.every((name) => typeof name === 'string')) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`extensions should be an array of names. Received ${JSON.stringify(extensions)}.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const base = [`${nativeVersion}-${profile}`, ...[...extensions].sort()].join('+');
|
|
103
|
+
if (plugins.length === 0) {
|
|
104
|
+
return base;
|
|
105
|
+
}
|
|
106
|
+
return `${base}-${hashPlugins(plugins)}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// `require('./runtimeVersion.js')` is the function, and `.getRuntimeVersion` names it too, so
|
|
110
|
+
// `lowdefy mobile` can load it from the copied shell either way.
|
|
111
|
+
module.exports = getRuntimeVersion;
|
|
112
|
+
module.exports.getRuntimeVersion = getRuntimeVersion;
|
|
@@ -0,0 +1,358 @@
|
|
|
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
|
+
/*
|
|
18
|
+
The release check: a shell carries only its capability profile's native modules (profiles.js),
|
|
19
|
+
and a customer shell none of Lowdefy Dev's developer features, in its JavaScript or in its native
|
|
20
|
+
projects. A shell's release build runs it before a store build.
|
|
21
|
+
|
|
22
|
+
For each release profile (basic, field, field-nfc, field-tracking; --profile names one) and each
|
|
23
|
+
platform, with LOWDEFY_SHELL_VARIANT=release and the shell config LOWDEFY_SHELL_CONFIG names
|
|
24
|
+
(Lowdefy Dev's shell.dev.json without it) set to that profile:
|
|
25
|
+
|
|
26
|
+
1. Bundle: exports the JavaScript bundle and fails when any file of it holds the dev-tab client's
|
|
27
|
+
marker (src/dev/tab/devClientMarker.js) or the native module name of a module the profile
|
|
28
|
+
leaves out (getExclusions in shellVariant.js).
|
|
29
|
+
2. Prebuild: runs `expo prebuild` in a copy of the shell and fails when
|
|
30
|
+
- Expo autolinking, run with the exclusions the generated Podfile and settings.gradle pass it,
|
|
31
|
+
still links a module the profile leaves out; or
|
|
32
|
+
- the native project asks for a permission only a left-out module needs
|
|
33
|
+
(NSCameraUsageDescription and android.permission.CAMERA without the camera,
|
|
34
|
+
NSMotionUsageDescription in any release shell).
|
|
35
|
+
|
|
36
|
+
With --control it first runs both on the dev variant (profile `dev`) and fails when the marker,
|
|
37
|
+
the module names or the modules any release profile leaves out are missing there, which shows the
|
|
38
|
+
check can see them.
|
|
39
|
+
|
|
40
|
+
node scripts/checkReleaseBundle.mjs [--platform ios|android] [--profile <name>] [--control]
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { execFileSync } from 'node:child_process';
|
|
44
|
+
import fs from 'node:fs';
|
|
45
|
+
import { createRequire } from 'node:module';
|
|
46
|
+
import os from 'node:os';
|
|
47
|
+
import path from 'node:path';
|
|
48
|
+
import { fileURLToPath } from 'node:url';
|
|
49
|
+
|
|
50
|
+
const shellRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
51
|
+
const require = createRequire(import.meta.url);
|
|
52
|
+
const { RELEASE_PROFILES } = require('../profiles.js');
|
|
53
|
+
const readShellConfig = require('../readShellConfig.js');
|
|
54
|
+
const { DEV_CLIENT_MARKER, getExclusions } = require('../shellVariant.js');
|
|
55
|
+
|
|
56
|
+
// The modules some release profile leaves out, which the dev variant links, for the --control run.
|
|
57
|
+
const RELEASE_EXCLUDED_MODULES = Object.assign(
|
|
58
|
+
{},
|
|
59
|
+
...RELEASE_PROFILES.map(
|
|
60
|
+
(profile) => getExclusions({ shell: { profile }, variant: 'release' }).modules
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
// The native module names a bundle that carries a module's JavaScript holds. A module with no
|
|
65
|
+
// JavaScript of its own in the bundle (null) is checked by autolinking only.
|
|
66
|
+
function nativeNames(modules) {
|
|
67
|
+
return Object.values(modules).filter((name) => name !== null);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Copied into each prebuild's temporary project; node_modules is linked, not copied.
|
|
71
|
+
const SKIPPED_IN_COPY = new Set(['android', 'coverage', 'dist', 'ios', 'node_modules', '.expo']);
|
|
72
|
+
|
|
73
|
+
const AUTOLINKING_PLATFORMS = { ios: 'apple', android: 'android' };
|
|
74
|
+
|
|
75
|
+
function readOption(argv, option) {
|
|
76
|
+
const index = argv.indexOf(option);
|
|
77
|
+
return index === -1 ? undefined : argv[index + 1];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readArgs(argv) {
|
|
81
|
+
const platform = readOption(argv, '--platform');
|
|
82
|
+
const profile = readOption(argv, '--profile');
|
|
83
|
+
if (profile !== undefined && !RELEASE_PROFILES.includes(profile)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`--profile should be one of ${RELEASE_PROFILES.join(', ')}. Received ${JSON.stringify(
|
|
86
|
+
profile
|
|
87
|
+
)}.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
control: argv.includes('--control'),
|
|
92
|
+
platforms: platform === undefined ? ['ios', 'android'] : [platform],
|
|
93
|
+
profiles: profile === undefined ? RELEASE_PROFILES : [profile],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The environment of a check of the shell built on one profile and variant: the shell config, set
|
|
98
|
+
// to that profile, written to a temporary shell.json, its icon still found where it is.
|
|
99
|
+
function withShellEnv({ configPath: shellConfigPath, profile, shell, variant }, callback) {
|
|
100
|
+
return withTemporaryDirectory(`lowdefy-shell-config-${profile}-`, (directory) => {
|
|
101
|
+
const configPath = path.join(directory, 'shell.json');
|
|
102
|
+
const icon =
|
|
103
|
+
shell.icon === undefined
|
|
104
|
+
? undefined
|
|
105
|
+
: path.resolve(path.dirname(shellConfigPath), shell.icon);
|
|
106
|
+
fs.writeFileSync(configPath, JSON.stringify({ ...shell, icon, profile }, null, 2));
|
|
107
|
+
return callback({
|
|
108
|
+
...process.env,
|
|
109
|
+
CI: '1',
|
|
110
|
+
LOWDEFY_SHELL_CONFIG: configPath,
|
|
111
|
+
LOWDEFY_SHELL_VARIANT: variant,
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function listFiles(directory) {
|
|
117
|
+
return fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
118
|
+
const entryPath = path.join(directory, entry.name);
|
|
119
|
+
return entry.isDirectory() ? listFiles(entryPath) : [entryPath];
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function withTemporaryDirectory(prefix, callback) {
|
|
124
|
+
const directory = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
125
|
+
try {
|
|
126
|
+
return callback(directory);
|
|
127
|
+
} finally {
|
|
128
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The files of the exported bundle that hold each string, as { [string]: [file] }. Hermes bytecode
|
|
133
|
+
// keeps string literals as plain bytes, so they are found in .hbc files too.
|
|
134
|
+
function findInBundle({ env, platform, strings }) {
|
|
135
|
+
return withTemporaryDirectory(`lowdefy-shell-bundle-${platform}-`, (outputDir) => {
|
|
136
|
+
execFileSync(
|
|
137
|
+
path.join(shellRoot, 'node_modules', '.bin', 'expo'),
|
|
138
|
+
['export', '--platform', platform, '--output-dir', outputDir, '--clear'],
|
|
139
|
+
{ cwd: shellRoot, env, stdio: ['ignore', 'ignore', 'inherit'] }
|
|
140
|
+
);
|
|
141
|
+
const files = listFiles(outputDir).map((file) => ({
|
|
142
|
+
name: path.relative(outputDir, file),
|
|
143
|
+
bytes: fs.readFileSync(file),
|
|
144
|
+
}));
|
|
145
|
+
return Object.fromEntries(
|
|
146
|
+
strings.map((string) => [
|
|
147
|
+
string,
|
|
148
|
+
files.filter(({ bytes }) => bytes.includes(Buffer.from(string))).map(({ name }) => name),
|
|
149
|
+
])
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function readList(text) {
|
|
155
|
+
return [...text.matchAll(/'([^']+)'/g)].map((match) => match[1]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// The exclusions a generated native project passes to Expo autolinking: [expo modules, react-native
|
|
159
|
+
// config]. iOS: the Podfile's `use_expo_modules!(exclude: [...])` and its react-native-config
|
|
160
|
+
// command's `--exclude` pairs. Android: settings.gradle's `expoAutolinking.exclude` and the list
|
|
161
|
+
// added to `rnConfigCommand`.
|
|
162
|
+
function readExclusions({ platform, projectDir }) {
|
|
163
|
+
if (platform === 'ios') {
|
|
164
|
+
const podfile = fs.readFileSync(path.join(projectDir, 'ios', 'Podfile'), 'utf8');
|
|
165
|
+
const expoModules = /use_expo_modules!\(exclude: \[([^\]]*)\]\)/.exec(podfile);
|
|
166
|
+
const reactNative = [...podfile.matchAll(/'--exclude', '([^']+)'/g)].map((match) => match[1]);
|
|
167
|
+
return { expo: expoModules ? readList(expoModules[1]) : [], reactNative };
|
|
168
|
+
}
|
|
169
|
+
const settings = fs.readFileSync(path.join(projectDir, 'android', 'settings.gradle'), 'utf8');
|
|
170
|
+
const expoModules = /expoAutolinking\.exclude = \[([^\]]*)\]/.exec(settings);
|
|
171
|
+
const reactNative = /rnConfigCommand \+ \[([^\]]*)\]/.exec(settings);
|
|
172
|
+
return {
|
|
173
|
+
expo: expoModules ? readList(expoModules[1]) : [],
|
|
174
|
+
reactNative: reactNative ? readList(reactNative[1]).filter((item) => item !== '--exclude') : [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function runAutolinking({ args, exclude, projectDir }) {
|
|
179
|
+
const output = execFileSync(
|
|
180
|
+
process.execPath,
|
|
181
|
+
[
|
|
182
|
+
'--no-warnings',
|
|
183
|
+
'--eval',
|
|
184
|
+
"require('expo/bin/autolinking')",
|
|
185
|
+
'expo-modules-autolinking',
|
|
186
|
+
...args,
|
|
187
|
+
...exclude.flatMap((name) => ['--exclude', name]),
|
|
188
|
+
],
|
|
189
|
+
{ cwd: projectDir, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }
|
|
190
|
+
);
|
|
191
|
+
return JSON.parse(output);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The packages the native build links: Expo modules and react-native-config dependencies.
|
|
195
|
+
function readLinkedModules({ platform, projectDir }) {
|
|
196
|
+
const exclusions = readExclusions({ platform, projectDir });
|
|
197
|
+
const resolved = runAutolinking({
|
|
198
|
+
args: ['resolve', '--json', '--platform', AUTOLINKING_PLATFORMS[platform]],
|
|
199
|
+
exclude: exclusions.expo,
|
|
200
|
+
projectDir,
|
|
201
|
+
});
|
|
202
|
+
const reactNative = runAutolinking({
|
|
203
|
+
args: ['react-native-config', '--json', '--platform', platform],
|
|
204
|
+
exclude: exclusions.reactNative,
|
|
205
|
+
projectDir,
|
|
206
|
+
});
|
|
207
|
+
return new Set([
|
|
208
|
+
...resolved.modules.map((module) => module.packageName),
|
|
209
|
+
...Object.keys(reactNative.dependencies),
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// The permissions the native project asks for, of those every profile's modules ask for: iOS
|
|
214
|
+
// Info.plist usage keys and Android permissions not removed by `blockedPermissions`.
|
|
215
|
+
function readPermissions({ platform, projectDir }) {
|
|
216
|
+
if (platform === 'ios') {
|
|
217
|
+
const iosDir = path.join(projectDir, 'ios');
|
|
218
|
+
const plist = fs
|
|
219
|
+
.readdirSync(iosDir)
|
|
220
|
+
.map((name) => path.join(iosDir, name, 'Info.plist'))
|
|
221
|
+
.find((file) => fs.existsSync(file));
|
|
222
|
+
const text = fs.readFileSync(plist, 'utf8');
|
|
223
|
+
return new Set(
|
|
224
|
+
[...text.matchAll(/<key>(NS[A-Za-z]+UsageDescription)<\/key>/g)].map((m) => m[1])
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const manifest = fs.readFileSync(
|
|
228
|
+
path.join(projectDir, 'android', 'app', 'src', 'main', 'AndroidManifest.xml'),
|
|
229
|
+
'utf8'
|
|
230
|
+
);
|
|
231
|
+
return new Set(
|
|
232
|
+
[...manifest.matchAll(/<uses-permission [^>]*android:name="([^"]+)"[^>]*>/g)]
|
|
233
|
+
.filter((match) => !match[0].includes('tools:node="remove"'))
|
|
234
|
+
.map((match) => match[1])
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function prebuild({ env, platform }) {
|
|
239
|
+
return withTemporaryDirectory(`lowdefy-shell-prebuild-${platform}-`, (projectDir) => {
|
|
240
|
+
fs.readdirSync(shellRoot)
|
|
241
|
+
.filter((name) => !SKIPPED_IN_COPY.has(name))
|
|
242
|
+
.forEach((name) => {
|
|
243
|
+
fs.cpSync(path.join(shellRoot, name), path.join(projectDir, name), { recursive: true });
|
|
244
|
+
});
|
|
245
|
+
fs.symlinkSync(path.join(shellRoot, 'node_modules'), path.join(projectDir, 'node_modules'));
|
|
246
|
+
execFileSync(
|
|
247
|
+
path.join(projectDir, 'node_modules', '.bin', 'expo'),
|
|
248
|
+
['prebuild', '--no-install', '--platform', platform],
|
|
249
|
+
{ cwd: projectDir, env, stdio: ['ignore', 'ignore', 'inherit'] }
|
|
250
|
+
);
|
|
251
|
+
return {
|
|
252
|
+
linked: readLinkedModules({ platform, projectDir }),
|
|
253
|
+
permissions: readPermissions({ platform, projectDir }),
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function checkControl({ configPath, platform, shell }) {
|
|
259
|
+
return withShellEnv({ configPath, profile: 'dev', shell, variant: 'dev' }, (env) => {
|
|
260
|
+
const problems = [];
|
|
261
|
+
const found = findInBundle({
|
|
262
|
+
env,
|
|
263
|
+
platform,
|
|
264
|
+
strings: [DEV_CLIENT_MARKER, ...nativeNames(RELEASE_EXCLUDED_MODULES)],
|
|
265
|
+
});
|
|
266
|
+
Object.entries(found)
|
|
267
|
+
.filter(([, files]) => files.length === 0)
|
|
268
|
+
.forEach(([string]) => problems.push(`the dev bundle does not hold "${string}"`));
|
|
269
|
+
const { linked } = prebuild({ env, platform });
|
|
270
|
+
Object.keys(RELEASE_EXCLUDED_MODULES)
|
|
271
|
+
.filter((name) => !linked.has(name))
|
|
272
|
+
.forEach((name) => problems.push(`the dev prebuild does not link ${name}`));
|
|
273
|
+
return problems;
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// iOS asks for permissions with Info.plist usage keys, Android with android.permission names.
|
|
278
|
+
function onPlatform({ permissions, platform }) {
|
|
279
|
+
return permissions.filter(
|
|
280
|
+
(permission) => permission.startsWith('android.permission.') === (platform === 'android')
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function checkRelease({ configPath, platform, profile, shell }) {
|
|
285
|
+
const exclusions = getExclusions({ shell: { ...shell, profile }, variant: 'release' });
|
|
286
|
+
const excluded = {
|
|
287
|
+
modules: exclusions.modules,
|
|
288
|
+
permissions: onPlatform({ permissions: exclusions.permissions, platform }),
|
|
289
|
+
};
|
|
290
|
+
return withShellEnv({ configPath, profile, shell, variant: 'release' }, (env) => {
|
|
291
|
+
const problems = [];
|
|
292
|
+
const found = findInBundle({
|
|
293
|
+
env,
|
|
294
|
+
platform,
|
|
295
|
+
strings: [DEV_CLIENT_MARKER, ...nativeNames(excluded.modules)],
|
|
296
|
+
});
|
|
297
|
+
Object.entries(found)
|
|
298
|
+
.filter(([, files]) => files.length > 0)
|
|
299
|
+
.forEach(([string, files]) =>
|
|
300
|
+
problems.push(`the release bundle holds "${string}" (${files.join(', ')})`)
|
|
301
|
+
);
|
|
302
|
+
const { linked, permissions } = prebuild({ env, platform });
|
|
303
|
+
Object.keys(excluded.modules)
|
|
304
|
+
.filter((name) => linked.has(name))
|
|
305
|
+
.forEach((name) => problems.push(`the release prebuild links ${name}`));
|
|
306
|
+
excluded.permissions
|
|
307
|
+
.filter((permission) => permissions.has(permission))
|
|
308
|
+
.forEach((permission) =>
|
|
309
|
+
problems.push(`the release prebuild asks for ${permission}, which no module it links needs`)
|
|
310
|
+
);
|
|
311
|
+
return { excluded, problems };
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function main() {
|
|
316
|
+
const { control, platforms, profiles } = readArgs(process.argv.slice(2));
|
|
317
|
+
const { configPath, shell } = readShellConfig();
|
|
318
|
+
let failed = false;
|
|
319
|
+
platforms.forEach((platform) => {
|
|
320
|
+
if (control) {
|
|
321
|
+
const problems = checkControl({ configPath, platform, shell });
|
|
322
|
+
if (problems.length > 0) {
|
|
323
|
+
problems.forEach((problem) => console.error(`${platform}: ${problem}.`));
|
|
324
|
+
failed = true;
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
console.log(
|
|
328
|
+
`${platform}: the dev variant holds the dev-tab client and links ${Object.keys(
|
|
329
|
+
RELEASE_EXCLUDED_MODULES
|
|
330
|
+
).join(', ')}.`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
profiles.forEach((profile) => {
|
|
334
|
+
const { excluded, problems } = checkRelease({ configPath, platform, profile, shell });
|
|
335
|
+
if (problems.length > 0) {
|
|
336
|
+
problems.forEach((problem) => console.error(`${platform} ${profile}: ${problem}.`));
|
|
337
|
+
failed = true;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const permissions =
|
|
341
|
+
excluded.permissions.length === 0
|
|
342
|
+
? 'asks for no permission of a left-out module'
|
|
343
|
+
: `asks for none of ${excluded.permissions.join(', ')}`;
|
|
344
|
+
console.log(
|
|
345
|
+
`${platform} ${profile}: the release shell "${
|
|
346
|
+
shell.id
|
|
347
|
+
}" holds no dev-tab client, links none of ${Object.keys(excluded.modules).join(
|
|
348
|
+
', '
|
|
349
|
+
)}, and ${permissions}.`
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
if (failed) {
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
main();
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
import fs from 'node:fs';
|
|
18
|
+
import { createRequire } from 'node:module';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
const shellRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
+
const require = createRequire(import.meta.url);
|
|
24
|
+
|
|
25
|
+
const HEADER = `/*
|
|
26
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
27
|
+
|
|
28
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
29
|
+
you may not use this file except in compliance with the License.
|
|
30
|
+
You may obtain a copy of the License at
|
|
31
|
+
|
|
32
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
33
|
+
|
|
34
|
+
Unless required by applicable law or agreed to in writing, software
|
|
35
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
36
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
37
|
+
See the License for the specific language governing permissions and
|
|
38
|
+
limitations under the License.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// Generated by scripts/createDayjsLocales.mjs from the dayjs package the shell carries. Do not edit.
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
// Every locale dayjs ships, as an app may name any of them in config.i18n.locales[].dayjs. Metro
|
|
45
|
+
// bundles each, and a locale's data is evaluated only when the app's active locale needs it.
|
|
46
|
+
function createDayjsLocales() {
|
|
47
|
+
const localeDirectory = path.join(path.dirname(require.resolve('dayjs/package.json')), 'locale');
|
|
48
|
+
const ids = fs
|
|
49
|
+
.readdirSync(localeDirectory)
|
|
50
|
+
.filter((file) => file.endsWith('.js'))
|
|
51
|
+
.map((file) => file.slice(0, -'.js'.length))
|
|
52
|
+
.sort();
|
|
53
|
+
// Keys are quoted only where an id is not an identifier, as Prettier writes them.
|
|
54
|
+
const entries = ids.map((id) => {
|
|
55
|
+
const key = /^[a-z]+$/.test(id) ? id : `'${id}'`;
|
|
56
|
+
return ` ${key}: () => require('dayjs/locale/${id}.js'),`;
|
|
57
|
+
});
|
|
58
|
+
return `${HEADER}\nconst dayjsLocales = {\n${entries.join(
|
|
59
|
+
'\n'
|
|
60
|
+
)}\n};\n\nexport default dayjsLocales;\n`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
64
|
+
fs.writeFileSync(path.join(shellRoot, 'src/locale/dayjsLocales.js'), createDayjsLocales());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default createDayjsLocales;
|