@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
|
@@ -0,0 +1,105 @@
|
|
|
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>`, and `-<plugins hash>` when the
|
|
68
|
+
* shell 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
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
function getRuntimeVersion({ nativeVersion, profile, plugins = [] }) {
|
|
85
|
+
if (!isPositiveInteger(nativeVersion)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`nativeVersion should be a positive integer. Received ${JSON.stringify(nativeVersion)}.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (typeof profile !== 'string' || profile.length === 0) {
|
|
91
|
+
throw new Error(`profile should be a string. Received ${JSON.stringify(profile)}.`);
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(plugins)) {
|
|
94
|
+
throw new Error(`plugins should be an array. Received ${JSON.stringify(plugins)}.`);
|
|
95
|
+
}
|
|
96
|
+
if (plugins.length === 0) {
|
|
97
|
+
return `${nativeVersion}-${profile}`;
|
|
98
|
+
}
|
|
99
|
+
return `${nativeVersion}-${profile}-${hashPlugins(plugins)}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// `require('./runtimeVersion.js')` is the function, and `.getRuntimeVersion` names it too, so
|
|
103
|
+
// `lowdefy mobile` can load it from the copied shell either way.
|
|
104
|
+
module.exports = getRuntimeVersion;
|
|
105
|
+
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;
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
Writes a shell's Android adaptive-icon foreground from its 1024×1024 icon: the icon scaled into
|
|
19
|
+
the centre 66% of a transparent 1024×1024 canvas, the safe zone a launcher's mask never cuts.
|
|
20
|
+
app.config.ts runs it when the shell's icon has no foreground yet (the app config is evaluated
|
|
21
|
+
synchronously, and image code is asynchronous).
|
|
22
|
+
|
|
23
|
+
node scripts/createShellAssets.mjs <icon.png> <foreground.png>
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import { createRequire } from 'node:module';
|
|
28
|
+
import { fileURLToPath } from 'node:url';
|
|
29
|
+
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
31
|
+
const Jimp = require('jimp-compact');
|
|
32
|
+
|
|
33
|
+
const SIZE = 1024;
|
|
34
|
+
const SAFE_ZONE = 0.66;
|
|
35
|
+
|
|
36
|
+
async function createAdaptiveIconForeground({ icon, output }) {
|
|
37
|
+
const image = await Jimp.read(icon);
|
|
38
|
+
const inner = Math.round(SIZE * SAFE_ZONE);
|
|
39
|
+
image.contain(inner, inner);
|
|
40
|
+
const canvas = new Jimp(SIZE, SIZE, 0x00000000);
|
|
41
|
+
canvas.composite(image, Math.round((SIZE - inner) / 2), Math.round((SIZE - inner) / 2));
|
|
42
|
+
await canvas.writeAsync(output);
|
|
43
|
+
return output;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (fs.realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
47
|
+
const [icon, output] = process.argv.slice(2);
|
|
48
|
+
if (icon === undefined || output === undefined) {
|
|
49
|
+
throw new Error('Usage: node scripts/createShellAssets.mjs <icon.png> <foreground.png>');
|
|
50
|
+
}
|
|
51
|
+
await createAdaptiveIconForeground({ icon, output });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { createAdaptiveIconForeground };
|
|
@@ -0,0 +1,83 @@
|
|
|
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 shell's version: the @lowdefy/shell package version, which the shell sends as
|
|
19
|
+
`x-lowdefy-shell` and compares with an app's `mobile.minShellVersion`
|
|
20
|
+
(src/version/isShellTooOld.js).
|
|
21
|
+
|
|
22
|
+
Written to src/version/shellVersion.js, which the shell's JavaScript imports
|
|
23
|
+
(src/config/getShellConfig.js). The version travels in the JavaScript bundle, not the Expo config,
|
|
24
|
+
so it is the version of the code that runs: an embedded launch reads the Expo config the native
|
|
25
|
+
build embedded, which a shell running newer JavaScript from Metro has outgrown. Only the
|
|
26
|
+
version is written, not package.json, which would put every dependency name in the bundle. The
|
|
27
|
+
file is committed; src/version/shellVersion.test.js fails when it is stale, and
|
|
28
|
+
`pnpm release:version` rewrites it after the version bump.
|
|
29
|
+
|
|
30
|
+
node scripts/createShellVersion.mjs prints the version
|
|
31
|
+
node scripts/createShellVersion.mjs --write writes src/version/shellVersion.js
|
|
32
|
+
|
|
33
|
+
Usage: pnpm --filter=@lowdefy/shell shell-version
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import fs from 'node:fs';
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
import { fileURLToPath } from 'node:url';
|
|
39
|
+
|
|
40
|
+
const shellRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
41
|
+
|
|
42
|
+
const HEADER = `/*
|
|
43
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
44
|
+
|
|
45
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
46
|
+
you may not use this file except in compliance with the License.
|
|
47
|
+
You may obtain a copy of the License at
|
|
48
|
+
|
|
49
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
50
|
+
|
|
51
|
+
Unless required by applicable law or agreed to in writing, software
|
|
52
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
53
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
54
|
+
See the License for the specific language governing permissions and
|
|
55
|
+
limitations under the License.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
// Generated by scripts/createShellVersion.mjs from package.json. Do not edit.
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
function createShellVersion() {
|
|
62
|
+
const { version } = JSON.parse(fs.readFileSync(path.join(shellRoot, 'package.json'), 'utf8'));
|
|
63
|
+
return version;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createShellVersionModule() {
|
|
67
|
+
return `${HEADER}\nconst shellVersion = ${JSON.stringify(
|
|
68
|
+
createShellVersion()
|
|
69
|
+
)};\n\nexport default shellVersion;\n`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
73
|
+
if (process.argv.includes('--write')) {
|
|
74
|
+
fs.writeFileSync(
|
|
75
|
+
path.join(shellRoot, 'src/version/shellVersion.js'),
|
|
76
|
+
createShellVersionModule()
|
|
77
|
+
);
|
|
78
|
+
} else {
|
|
79
|
+
process.stdout.write(createShellVersion());
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default createShellVersion;
|