@lowdefy/shell 0.0.0-experimental-20261019052246
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 +201 -0
- package/README.md +308 -0
- 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 +30 -0
- package/app/_layout.js +33 -0
- package/app.config.ts +168 -0
- package/applyShellPlugins.js +43 -0
- package/authCallbackIntentFilters.js +46 -0
- package/babel.config.js +25 -0
- package/checkShellConfig.js +145 -0
- package/checkShellPlugins.js +74 -0
- package/getPrivacyManifest.js +39 -0
- package/metro.config.js +73 -0
- package/native-fingerprint.json +12 -0
- package/package.json +130 -0
- package/profiles.js +274 -0
- package/readShellConfig.js +36 -0
- package/resolveShellPlugins.js +109 -0
- package/runtimeVersion.js +105 -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 +111 -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 +55 -0
- package/src/actions/GeolocationCurrentPosition.test.js +66 -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 +256 -0
- package/src/auth/SignInScreen.test.js +182 -0
- package/src/auth/createAppAuth.js +306 -0
- package/src/auth/createAppAuth.test.js +414 -0
- package/src/auth/createAuthApi.js +99 -0
- package/src/auth/createAuthApi.test.js +135 -0
- package/src/auth/createCoreAuth.js +96 -0
- package/src/auth/createCoreAuth.test.js +188 -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 +45 -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 +21 -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 +90 -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 +108 -0
- package/src/components/MenuButton.test.js +51 -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 +56 -0
- package/src/components/PageScreen.js +244 -0
- package/src/components/PageScreen.test.js +492 -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 +36 -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 +31 -0
- package/src/config/getShellConfig.js +35 -0
- package/src/config/getShellConfig.test.js +80 -0
- package/src/createShellApp.js +105 -0
- package/src/dev/DevShell.js +228 -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 +185 -0
- package/src/dev/inject/createDeviceInjections.test.js +208 -0
- package/src/dev/inject/createOfflineFetch.js +35 -0
- package/src/dev/inject/createOfflineFetch.test.js +68 -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/answerLoadState.js +50 -0
- package/src/dev/tab/createDevTabClient.js +277 -0
- package/src/dev/tab/createDevTabClient.test.js +314 -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/injected.js +60 -0
- package/src/device/injected.test.js +39 -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 +60 -0
- package/src/host/createShellFetch.test.js +150 -0
- package/src/host/createShellGlobals.js +62 -0
- package/src/host/createShellHost.js +73 -0
- package/src/host/createShellHost.test.js +36 -0
- package/src/host/getOrigin.js +26 -0
- package/src/host/isOnline.js +22 -0
- package/src/host/networkStatus.js +24 -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/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/toRouterHref.js +29 -0
- package/src/plugins/appTypes.js +28 -0
- package/src/plugins/prefixTypes.js +28 -0
- package/src/plugins/shellPlugins.json +14 -0
- package/src/plugins/typeManifest.js +649 -0
- package/src/plugins/types.js +61 -0
- package/src/plugins/types.test.js +173 -0
- package/src/polyfills/installGetRandomValues.js +28 -0
- package/src/session/createAppSession.js +318 -0
- package/src/session/createAppSession.test.js +359 -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 +49 -0
- package/src/sync/fetchRoot.js +38 -0
- package/src/sync/fetchUser.js +28 -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/expoImage.js +38 -0
- package/src/test/expoSpeechRecognition.js +31 -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/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/withExcludedNativeModules.js +93 -0
package/metro.config.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
const { getDefaultConfig } = require('expo/metro-config');
|
|
20
|
+
|
|
21
|
+
const readShellConfig = require('./readShellConfig.js');
|
|
22
|
+
const { getExclusions, getShellVariant } = require('./shellVariant.js');
|
|
23
|
+
|
|
24
|
+
const projectRoot = __dirname;
|
|
25
|
+
|
|
26
|
+
function findWorkspaceRoot(directory) {
|
|
27
|
+
if (fs.existsSync(path.join(directory, 'pnpm-workspace.yaml'))) {
|
|
28
|
+
return directory;
|
|
29
|
+
}
|
|
30
|
+
const parent = path.dirname(directory);
|
|
31
|
+
return parent === directory ? null : findWorkspaceRoot(parent);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const config = getDefaultConfig(projectRoot);
|
|
35
|
+
config.resolver.unstable_enableSymlinks = true;
|
|
36
|
+
|
|
37
|
+
// A shell resolves the feature modules its capability profile leaves out to empty ones
|
|
38
|
+
// (getExclusions in shellVariant.js). A release shell (LOWDEFY_SHELL_VARIANT=release, the default
|
|
39
|
+
// for every shell but Lowdefy Dev) leaves out the developer features: the dev-tab client, connect
|
|
40
|
+
// flow and shake menu are not in its bundle (scripts/checkReleaseBundle.mjs checks it).
|
|
41
|
+
const excludedEntries = new Set(
|
|
42
|
+
getExclusions({ shell: readShellConfig().shell, variant: getShellVariant() }).jsEntries
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Lowdefy packages shared with the server import a few Node built-ins in code the client never
|
|
46
|
+
// runs (@lowdefy/errors reads build maps with `path`). The web client's bundler leaves them empty
|
|
47
|
+
// in the browser; Metro does the same here.
|
|
48
|
+
const SERVER_ONLY_BUILTINS = new Set(['path']);
|
|
49
|
+
const resolveDefault = config.resolver.resolveRequest;
|
|
50
|
+
config.resolver.resolveRequest = (context, moduleName, platform) => {
|
|
51
|
+
if (SERVER_ONLY_BUILTINS.has(moduleName)) {
|
|
52
|
+
return { type: 'empty' };
|
|
53
|
+
}
|
|
54
|
+
const resolution = (resolveDefault ?? context.resolveRequest)(context, moduleName, platform);
|
|
55
|
+
if (resolution.type === 'sourceFile' && excludedEntries.has(resolution.filePath)) {
|
|
56
|
+
return { type: 'empty' };
|
|
57
|
+
}
|
|
58
|
+
return resolution;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// In a pnpm workspace (the Lowdefy monorepo) the shell's Lowdefy packages are symlinks to their
|
|
62
|
+
// workspace folders: Metro watches the whole workspace and resolves from the shell's own
|
|
63
|
+
// node_modules before the workspace root's.
|
|
64
|
+
const workspaceRoot = findWorkspaceRoot(path.dirname(projectRoot));
|
|
65
|
+
if (workspaceRoot !== null) {
|
|
66
|
+
config.watchFolders = [workspaceRoot];
|
|
67
|
+
config.resolver.nodeModulesPaths = [
|
|
68
|
+
path.join(projectRoot, 'node_modules'),
|
|
69
|
+
path.join(workspaceRoot, 'node_modules'),
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = config;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nativeVersion": 2,
|
|
3
|
+
"hash": "32a477d7fe75d305f87e57d1f633f2fe6d3f9a59",
|
|
4
|
+
"profiles": {
|
|
5
|
+
"basic": "cd8736936deaf720f1427407d9401b1f900a9d75",
|
|
6
|
+
"field": "5375e8ffcc85dfb3a9c0f9abc0b7ee6cd1dfeea4",
|
|
7
|
+
"field-nfc": "5375e8ffcc85dfb3a9c0f9abc0b7ee6cd1dfeea4",
|
|
8
|
+
"field-tracking": "5375e8ffcc85dfb3a9c0f9abc0b7ee6cd1dfeea4",
|
|
9
|
+
"dev": "654adfb2adfcd0c77034176aa02ac36c20a449c2",
|
|
10
|
+
"development": "654adfb2adfcd0c77034176aa02ac36c20a449c2"
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lowdefy/shell",
|
|
3
|
+
"version": "0.0.0-experimental-20261019052246",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"description": "Lowdefy Shell - the Expo app every Lowdefy phone app is built from",
|
|
6
|
+
"homepage": "https://lowdefy.com",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"lowdefy",
|
|
9
|
+
"react native",
|
|
10
|
+
"expo"
|
|
11
|
+
],
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/lowdefy/lowdefy/issues"
|
|
14
|
+
},
|
|
15
|
+
"contributors": [
|
|
16
|
+
{
|
|
17
|
+
"name": "Sam Tolmay",
|
|
18
|
+
"url": "https://github.com/SamTolmay"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "Gerrie van Wyk",
|
|
22
|
+
"url": "https://github.com/Gervwyk"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/lowdefy/lowdefy.git"
|
|
28
|
+
},
|
|
29
|
+
"main": "expo-router/entry",
|
|
30
|
+
"nativeVersion": 2,
|
|
31
|
+
"files": [
|
|
32
|
+
"app/*",
|
|
33
|
+
"scripts/*",
|
|
34
|
+
"src/*",
|
|
35
|
+
"app.config.ts",
|
|
36
|
+
"applyShellPlugins.js",
|
|
37
|
+
"authCallbackIntentFilters.js",
|
|
38
|
+
"babel.config.js",
|
|
39
|
+
"checkShellConfig.js",
|
|
40
|
+
"checkShellPlugins.js",
|
|
41
|
+
"getPrivacyManifest.js",
|
|
42
|
+
"metro.config.js",
|
|
43
|
+
"native-fingerprint.json",
|
|
44
|
+
"profiles.js",
|
|
45
|
+
"readShellConfig.js",
|
|
46
|
+
"resolveShellPlugins.js",
|
|
47
|
+
"runtimeVersion.js",
|
|
48
|
+
"shell.dev.json",
|
|
49
|
+
"shellVariant.js",
|
|
50
|
+
"withExcludedNativeModules.js",
|
|
51
|
+
"!**/*.test.*",
|
|
52
|
+
"!scripts/checkNativeVersion.mjs"
|
|
53
|
+
],
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@expo/ui": "58.0.20",
|
|
56
|
+
"@lowdefy/actions-core": "0.0.0-experimental-20261019052246",
|
|
57
|
+
"@lowdefy/blocks-native": "0.0.0-experimental-20261019052246",
|
|
58
|
+
"@lowdefy/client-core": "0.0.0-experimental-20261019052246",
|
|
59
|
+
"@lowdefy/client-native": "0.0.0-experimental-20261019052246",
|
|
60
|
+
"@lowdefy/engine": "0.0.0-experimental-20261019052246",
|
|
61
|
+
"@lowdefy/helpers": "0.0.0-experimental-20261019052246",
|
|
62
|
+
"@lowdefy/node-utils": "0.0.0-experimental-20261019052246",
|
|
63
|
+
"@lowdefy/operators-change-case": "0.0.0-experimental-20261019052246",
|
|
64
|
+
"@lowdefy/operators-cron": "0.0.0-experimental-20261019052246",
|
|
65
|
+
"@lowdefy/operators-dayjs": "0.0.0-experimental-20261019052246",
|
|
66
|
+
"@lowdefy/operators-diff": "0.0.0-experimental-20261019052246",
|
|
67
|
+
"@lowdefy/operators-js": "0.0.0-experimental-20261019052246",
|
|
68
|
+
"@lowdefy/operators-jsonata": "0.0.0-experimental-20261019052246",
|
|
69
|
+
"@lowdefy/operators-mql": "0.0.0-experimental-20261019052246",
|
|
70
|
+
"@lowdefy/operators-nunjucks": "0.0.0-experimental-20261019052246",
|
|
71
|
+
"@lowdefy/operators-uuid": "0.0.0-experimental-20261019052246",
|
|
72
|
+
"@lowdefy/operators-yaml": "0.0.0-experimental-20261019052246",
|
|
73
|
+
"dayjs": "1.11.20",
|
|
74
|
+
"expo": "58.0.14",
|
|
75
|
+
"expo-clipboard": "58.0.3",
|
|
76
|
+
"expo-camera": "58.0.10",
|
|
77
|
+
"expo-constants": "58.0.12",
|
|
78
|
+
"expo-crypto": "58.0.7",
|
|
79
|
+
"expo-dev-client": "58.0.14",
|
|
80
|
+
"expo-font": "58.0.8",
|
|
81
|
+
"expo-image": "58.0.15",
|
|
82
|
+
"expo-linking": "58.0.13",
|
|
83
|
+
"expo-local-authentication": "58.0.2",
|
|
84
|
+
"expo-network": "58.0.3",
|
|
85
|
+
"expo-router": "58.0.23",
|
|
86
|
+
"expo-secure-store": "58.0.2",
|
|
87
|
+
"expo-sensors": "58.0.4",
|
|
88
|
+
"expo-speech-recognition": "58.0.0",
|
|
89
|
+
"expo-splash-screen": "58.0.10",
|
|
90
|
+
"expo-sqlite": "58.0.13",
|
|
91
|
+
"expo-updates": "58.0.17",
|
|
92
|
+
"expo-web-browser": "58.0.6",
|
|
93
|
+
"jimp-compact": "0.16.1",
|
|
94
|
+
"react": "19.3.0",
|
|
95
|
+
"react-native": "0.88.0",
|
|
96
|
+
"react-native-safe-area-context": "5.10.1",
|
|
97
|
+
"react-native-screens": "4.30.0",
|
|
98
|
+
"react-native-svg": "15.16.0",
|
|
99
|
+
"react-native-view-shot": "6.1.1"
|
|
100
|
+
},
|
|
101
|
+
"devDependencies": {
|
|
102
|
+
"@babel/core": "7.29.6",
|
|
103
|
+
"@babel/plugin-transform-class-static-block": "7.29.7",
|
|
104
|
+
"@expo/fingerprint": "0.21.5",
|
|
105
|
+
"@jest/globals": "29.7.0",
|
|
106
|
+
"@lowdefy/build": "0.0.0-experimental-20261019052246",
|
|
107
|
+
"@lowdefy/logger": "0.0.0-experimental-20261019052246",
|
|
108
|
+
"@react-native/babel-preset": "0.88.0",
|
|
109
|
+
"@react-native/jest-preset": "0.88.0",
|
|
110
|
+
"@testing-library/react-native": "14.1.0",
|
|
111
|
+
"babel-jest": "29.7.0",
|
|
112
|
+
"babel-preset-expo": "58.0.17",
|
|
113
|
+
"jest": "29.7.0",
|
|
114
|
+
"test-renderer": "1.3.1"
|
|
115
|
+
},
|
|
116
|
+
"publishConfig": {
|
|
117
|
+
"access": "public"
|
|
118
|
+
},
|
|
119
|
+
"scripts": {
|
|
120
|
+
"dayjs-locales": "node scripts/createDayjsLocales.mjs",
|
|
121
|
+
"check:native-version": "node scripts/checkNativeVersion.mjs",
|
|
122
|
+
"check:release-bundle": "node scripts/checkReleaseBundle.mjs",
|
|
123
|
+
"export:android": "expo export --platform android --output-dir dist/android",
|
|
124
|
+
"export:ios": "expo export --platform ios --output-dir dist/ios",
|
|
125
|
+
"start": "expo start",
|
|
126
|
+
"test": "jest && node --test fixtures/hello.test.mjs scripts/createDayjsLocales.test.mjs checkShellConfig.test.mjs checkShellPlugins.test.mjs profiles.test.mjs resolveShellPlugins.test.mjs runtimeVersion.test.mjs shellVariant.test.mjs storeCompliance.test.mjs authCallbackIntentFilters.test.mjs scripts/checkNativeVersion.test.mjs scripts/createTypeManifest.test.mjs",
|
|
127
|
+
"shell-version": "node scripts/createShellVersion.mjs --write && prettier --config ../../.prettierrc --write src/version/shellVersion.js",
|
|
128
|
+
"type-manifest": "node scripts/createTypeManifest.mjs --write && prettier --config ../../.prettierrc --write src/plugins/typeManifest.js"
|
|
129
|
+
}
|
|
130
|
+
}
|
package/profiles.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
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
|
+
- capabilities: the device features it gives the app (shell.json `capabilities`).
|
|
27
|
+
- modules: { [package]: nativeModuleName }, the native modules it links. The name is the one the
|
|
28
|
+
module's JavaScript asks for, which scripts/checkReleaseBundle.mjs looks for in a bundle that
|
|
29
|
+
should not carry it; null for a module with no JavaScript of its own in the bundle.
|
|
30
|
+
- plugins: the config plugin entries app.config.ts applies. A profile's entry for a plugin its
|
|
31
|
+
parent also has replaces the parent's.
|
|
32
|
+
- permissions: the iOS Info.plist keys and Android permissions its modules ask for.
|
|
33
|
+
- jsEntries: the feature modules only it uses. A bundle for any other profile resolves them to
|
|
34
|
+
empty modules (metro.config.js).
|
|
35
|
+
- privacyReasons: the required-reason APIs its modules use, for the iOS privacy manifest, as
|
|
36
|
+
{ category, reasons } (Apple's NSPrivacyAccessedAPIType and NSPrivacyAccessedAPITypeReasons).
|
|
37
|
+
- dataCollected: the data its features let the app collect, for the App Store privacy label and
|
|
38
|
+
Google Play's data safety form, as { type, purpose }.
|
|
39
|
+
|
|
40
|
+
Every native module the shell package depends on is in some profile. A build leaves out every one
|
|
41
|
+
not in its profile (getExclusions in shellVariant.js), and a release build also leaves out the
|
|
42
|
+
developer features (`dev`'s own entries), whatever its profile.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
// Lowdefy Dev's developer features: connect, shake menu and dev-tab client (src/dev).
|
|
46
|
+
const DEV_ENTRY = path.join(__dirname, 'src', 'dev', 'index.js');
|
|
47
|
+
|
|
48
|
+
// One microphone purpose text for every plugin that writes NSMicrophoneUsageDescription: speech
|
|
49
|
+
// (basic) and expo-camera (field, dev). expo-camera writes its own default when no earlier plugin
|
|
50
|
+
// set the key, so it is given the same text and plugin order cannot change what a shell shows.
|
|
51
|
+
const MICROPHONE_PERMISSION =
|
|
52
|
+
'$(PRODUCT_NAME) uses the microphone when you tap the microphone button, to turn what you say into text.';
|
|
53
|
+
const SPEECH_RECOGNITION_PERMISSION =
|
|
54
|
+
'$(PRODUCT_NAME) turns what you say into text on this device. Your voice is not recorded or sent anywhere.';
|
|
55
|
+
|
|
56
|
+
const PROFILES = {
|
|
57
|
+
// What every shell carries: the runtime, native blocks, sign-in, biometric unlock and on-device
|
|
58
|
+
// dictation (expo-speech-recognition, for the Composer and AgentChat mic).
|
|
59
|
+
basic: {
|
|
60
|
+
capabilities: ['biometrics', 'sign-in', 'speech'],
|
|
61
|
+
modules: {
|
|
62
|
+
'@expo/ui': 'ExpoUI',
|
|
63
|
+
'expo-clipboard': 'ExpoClipboard',
|
|
64
|
+
'expo-constants': 'ExponentConstants',
|
|
65
|
+
'expo-crypto': 'ExpoCrypto',
|
|
66
|
+
'expo-font': 'ExpoFontLoader',
|
|
67
|
+
'expo-image': 'ExpoImage',
|
|
68
|
+
'expo-linking': 'ExpoLinking',
|
|
69
|
+
'expo-local-authentication': 'ExpoLocalAuthentication',
|
|
70
|
+
'expo-network': 'ExpoNetwork',
|
|
71
|
+
'expo-router': null,
|
|
72
|
+
'expo-secure-store': 'ExpoSecureStore',
|
|
73
|
+
'expo-speech-recognition': 'ExpoSpeechRecognition',
|
|
74
|
+
'expo-splash-screen': 'ExpoSplashScreen',
|
|
75
|
+
'expo-sqlite': 'ExpoSQLite',
|
|
76
|
+
'expo-updates': 'ExpoUpdates',
|
|
77
|
+
'expo-web-browser': 'ExpoWebBrowser',
|
|
78
|
+
'react-native-safe-area-context': 'RNCSafeAreaContext',
|
|
79
|
+
'react-native-screens': 'RNSModule',
|
|
80
|
+
'react-native-svg': 'RNSVGSvgViewModule',
|
|
81
|
+
},
|
|
82
|
+
plugins: [
|
|
83
|
+
'expo-router',
|
|
84
|
+
[
|
|
85
|
+
'expo-local-authentication',
|
|
86
|
+
{ faceIDPermission: 'Allow $(PRODUCT_NAME) to use Face ID to unlock the app.' },
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
'expo-secure-store',
|
|
90
|
+
{ faceIDPermission: 'Allow $(PRODUCT_NAME) to use Face ID to unlock the app.' },
|
|
91
|
+
],
|
|
92
|
+
[
|
|
93
|
+
'expo-speech-recognition',
|
|
94
|
+
{
|
|
95
|
+
microphonePermission: MICROPHONE_PERMISSION,
|
|
96
|
+
speechRecognitionPermission: SPEECH_RECOGNITION_PERMISSION,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
'expo-splash-screen',
|
|
100
|
+
'expo-sqlite',
|
|
101
|
+
'expo-web-browser',
|
|
102
|
+
],
|
|
103
|
+
permissions: [
|
|
104
|
+
'NSFaceIDUsageDescription',
|
|
105
|
+
'android.permission.USE_BIOMETRIC',
|
|
106
|
+
'NSMicrophoneUsageDescription',
|
|
107
|
+
'NSSpeechRecognitionUsageDescription',
|
|
108
|
+
'android.permission.RECORD_AUDIO',
|
|
109
|
+
],
|
|
110
|
+
jsEntries: [],
|
|
111
|
+
// React Native's own (file timestamps, UserDefaults, boot time), and the free disk space
|
|
112
|
+
// expo-sqlite checks before it writes.
|
|
113
|
+
privacyReasons: [
|
|
114
|
+
{ category: 'NSPrivacyAccessedAPICategoryFileTimestamp', reasons: ['C617.1'] },
|
|
115
|
+
{ category: 'NSPrivacyAccessedAPICategoryUserDefaults', reasons: ['CA92.1'] },
|
|
116
|
+
{ category: 'NSPrivacyAccessedAPICategorySystemBootTime', reasons: ['35F9.1'] },
|
|
117
|
+
{ category: 'NSPrivacyAccessedAPICategoryDiskSpace', reasons: ['E174.1'] },
|
|
118
|
+
],
|
|
119
|
+
dataCollected: [
|
|
120
|
+
{ type: 'emailAddress', purpose: 'appFunctionality' },
|
|
121
|
+
{ type: 'userId', purpose: 'appFunctionality' },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
// Field work: the camera and scanning. Photos, location, maps and signatures join it as the
|
|
125
|
+
// shell gains them.
|
|
126
|
+
field: {
|
|
127
|
+
extends: 'basic',
|
|
128
|
+
capabilities: ['camera'],
|
|
129
|
+
modules: { 'expo-camera': 'ExpoCamera' },
|
|
130
|
+
// The camera does not add Android's microphone permission: the shell takes no video.
|
|
131
|
+
// RECORD_AUDIO comes from basic, for speech.
|
|
132
|
+
plugins: [
|
|
133
|
+
[
|
|
134
|
+
'expo-camera',
|
|
135
|
+
{
|
|
136
|
+
cameraPermission: 'Allow $(PRODUCT_NAME) to use the camera.',
|
|
137
|
+
microphonePermission: MICROPHONE_PERMISSION,
|
|
138
|
+
recordAudioAndroid: false,
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
],
|
|
142
|
+
permissions: ['NSCameraUsageDescription', 'android.permission.CAMERA'],
|
|
143
|
+
dataCollected: [{ type: 'photos', purpose: 'appFunctionality' }],
|
|
144
|
+
},
|
|
145
|
+
// `field` and NFC, which joins it as the shell gains it.
|
|
146
|
+
'field-nfc': {
|
|
147
|
+
extends: 'field',
|
|
148
|
+
},
|
|
149
|
+
// `field` and background location and geofences, which join it as the shell gains them. Its
|
|
150
|
+
// session token stays readable while the phone is locked (src/auth/isBackgroundShell.js).
|
|
151
|
+
'field-tracking': {
|
|
152
|
+
extends: 'field',
|
|
153
|
+
},
|
|
154
|
+
// Lowdefy Dev: `field` and the developer features, which read the code `lowdefy dev` prints with
|
|
155
|
+
// the camera, open the dev menu on a shake (expo-sensors) and take screenshots for the dev tab
|
|
156
|
+
// (react-native-view-shot). expo-dev-client lets a development build load a local bundle.
|
|
157
|
+
dev: {
|
|
158
|
+
extends: 'field',
|
|
159
|
+
capabilities: [],
|
|
160
|
+
modules: {
|
|
161
|
+
'expo-dev-client': null,
|
|
162
|
+
'expo-sensors': 'ExponentAccelerometer',
|
|
163
|
+
'react-native-view-shot': 'RNViewShot',
|
|
164
|
+
},
|
|
165
|
+
plugins: [
|
|
166
|
+
[
|
|
167
|
+
'expo-camera',
|
|
168
|
+
{
|
|
169
|
+
cameraPermission: 'Allow $(PRODUCT_NAME) to scan the code lowdefy dev prints.',
|
|
170
|
+
microphonePermission: MICROPHONE_PERMISSION,
|
|
171
|
+
recordAudioAndroid: false,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
],
|
|
175
|
+
permissions: ['NSMotionUsageDescription'],
|
|
176
|
+
jsEntries: [DEV_ENTRY],
|
|
177
|
+
},
|
|
178
|
+
// A development build of a developer's own shell (`lowdefy mobile build --profile development`):
|
|
179
|
+
// Lowdefy Dev's features, for apps whose shell plugins Lowdefy Dev does not carry.
|
|
180
|
+
development: {
|
|
181
|
+
extends: 'dev',
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// The profiles a store or testing build of an app's shell picks from.
|
|
186
|
+
const RELEASE_PROFILES = ['basic', 'field', 'field-nfc', 'field-tracking'];
|
|
187
|
+
|
|
188
|
+
function pluginName(plugin) {
|
|
189
|
+
return Array.isArray(plugin) ? plugin[0] : plugin;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function unique(values) {
|
|
193
|
+
return [...new Set(values)];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Merges two resolved profiles, or a profile entry onto its resolved parent: lists are joined
|
|
197
|
+
// without duplicates, and `added`'s modules and plugins replace `base`'s of the same name.
|
|
198
|
+
function mergeProfiles(base, added) {
|
|
199
|
+
const plugins = [...base.plugins];
|
|
200
|
+
(added.plugins ?? []).forEach((plugin) => {
|
|
201
|
+
const index = plugins.findIndex((existing) => pluginName(existing) === pluginName(plugin));
|
|
202
|
+
if (index === -1) {
|
|
203
|
+
plugins.push(plugin);
|
|
204
|
+
} else {
|
|
205
|
+
plugins[index] = plugin;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
const privacyReasons = base.privacyReasons.map((reason) => ({ ...reason }));
|
|
209
|
+
(added.privacyReasons ?? []).forEach(({ category, reasons }) => {
|
|
210
|
+
const existing = privacyReasons.find((reason) => reason.category === category);
|
|
211
|
+
if (existing) {
|
|
212
|
+
existing.reasons = unique([...existing.reasons, ...reasons]);
|
|
213
|
+
} else {
|
|
214
|
+
privacyReasons.push({ category, reasons: [...reasons] });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
const dataCollected = [...base.dataCollected];
|
|
218
|
+
(added.dataCollected ?? []).forEach((data) => {
|
|
219
|
+
if (!dataCollected.some(({ type }) => type === data.type)) {
|
|
220
|
+
dataCollected.push(data);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
name: added.name ?? base.name,
|
|
225
|
+
capabilities: unique([...base.capabilities, ...(added.capabilities ?? [])]),
|
|
226
|
+
modules: { ...base.modules, ...added.modules },
|
|
227
|
+
plugins,
|
|
228
|
+
permissions: unique([...base.permissions, ...(added.permissions ?? [])]),
|
|
229
|
+
jsEntries: unique([...base.jsEntries, ...(added.jsEntries ?? [])]),
|
|
230
|
+
privacyReasons,
|
|
231
|
+
dataCollected,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const EMPTY_PROFILE = {
|
|
236
|
+
capabilities: [],
|
|
237
|
+
modules: {},
|
|
238
|
+
plugins: [],
|
|
239
|
+
permissions: [],
|
|
240
|
+
jsEntries: [],
|
|
241
|
+
privacyReasons: [],
|
|
242
|
+
dataCollected: [],
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Everything a capability profile carries, with the profiles it extends folded in.
|
|
247
|
+
*
|
|
248
|
+
* @param {string} name The profile's name (shell.json `profile`).
|
|
249
|
+
* @returns {{ name, capabilities, modules, plugins, permissions, jsEntries, privacyReasons,
|
|
250
|
+
* dataCollected }}
|
|
251
|
+
*/
|
|
252
|
+
function resolveProfile(name) {
|
|
253
|
+
if (!Object.prototype.hasOwnProperty.call(PROFILES, name)) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`Shell profile ${JSON.stringify(name)} does not exist. Use one of ${Object.keys(
|
|
256
|
+
PROFILES
|
|
257
|
+
).join(', ')}.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const entry = PROFILES[name];
|
|
261
|
+
const base = entry.extends ? resolveProfile(entry.extends) : EMPTY_PROFILE;
|
|
262
|
+
return mergeProfiles(base, { ...entry, name });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
DEV_ENTRY,
|
|
267
|
+
MICROPHONE_PERMISSION,
|
|
268
|
+
PROFILES,
|
|
269
|
+
RELEASE_PROFILES,
|
|
270
|
+
mergeProfiles,
|
|
271
|
+
pluginName,
|
|
272
|
+
resolveProfile,
|
|
273
|
+
SPEECH_RECOGNITION_PERMISSION,
|
|
274
|
+
};
|
|
@@ -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;
|
|
@@ -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;
|