@axonpack/preview 0.0.0-stage → 0.1.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +118 -2
- package/android/build.gradle +21 -0
- package/android/consumer-rules.pro +3 -0
- package/android/src/main/java/expo/modules/axonpackpreview/AxonpackPreviewModule.kt +72 -0
- package/android/src/main/java/expo/modules/axonpackpreview/BundleLoaderOverride.kt +41 -0
- package/android/src/main/java/expo/modules/axonpackpreview/HermesHeader.kt +24 -0
- package/android/src/main/java/expo/modules/axonpackpreview/PreviewPackage.kt +76 -0
- package/android/src/main/java/expo/modules/axonpackpreview/PreviewState.kt +295 -0
- package/app.plugin.js +112 -0
- package/bin/preview.js +2 -0
- package/build/cli.cjs +1465 -0
- package/build/cli.d.ts +3 -0
- package/build/cli.d.ts.map +1 -0
- package/build/cli.js +164 -0
- package/build/cli.js.map +1 -0
- package/build/components/preview-list/preview-list.component.d.ts +13 -0
- package/build/components/preview-list/preview-list.component.d.ts.map +1 -0
- package/build/components/preview-list/preview-list.component.js +316 -0
- package/build/components/preview-list/preview-list.component.js.map +1 -0
- package/build/components/preview-list/preview-list.types.d.ts +59 -0
- package/build/components/preview-list/preview-list.types.d.ts.map +1 -0
- package/build/components/preview-list/preview-list.types.js +2 -0
- package/build/components/preview-list/preview-list.types.js.map +1 -0
- package/build/components/preview-provider/preview-provider.component.d.ts +12 -0
- package/build/components/preview-provider/preview-provider.component.d.ts.map +1 -0
- package/build/components/preview-provider/preview-provider.component.js +19 -0
- package/build/components/preview-provider/preview-provider.component.js.map +1 -0
- package/build/constants/dev-preview-servers/dev-preview-servers.const.d.ts +26 -0
- package/build/constants/dev-preview-servers/dev-preview-servers.const.d.ts.map +1 -0
- package/build/constants/dev-preview-servers/dev-preview-servers.const.js +9 -0
- package/build/constants/dev-preview-servers/dev-preview-servers.const.js.map +1 -0
- package/build/constants/preview-list/theme.const.d.ts +5 -0
- package/build/constants/preview-list/theme.const.d.ts.map +1 -0
- package/build/constants/preview-list/theme.const.js +65 -0
- package/build/constants/preview-list/theme.const.js.map +1 -0
- package/build/index.d.ts +24 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +16 -0
- package/build/index.js.map +1 -0
- package/build/services/cli/config.service.d.ts +13 -0
- package/build/services/cli/config.service.d.ts.map +1 -0
- package/build/services/cli/config.service.js +39 -0
- package/build/services/cli/config.service.js.map +1 -0
- package/build/services/cli/project.service.d.ts +25 -0
- package/build/services/cli/project.service.d.ts.map +1 -0
- package/build/services/cli/project.service.js +81 -0
- package/build/services/cli/project.service.js.map +1 -0
- package/build/services/cli/title.service.d.ts +15 -0
- package/build/services/cli/title.service.d.ts.map +1 -0
- package/build/services/cli/title.service.js +24 -0
- package/build/services/cli/title.service.js.map +1 -0
- package/build/services/dev-preview-servers/discover.service.d.ts +14 -0
- package/build/services/dev-preview-servers/discover.service.d.ts.map +1 -0
- package/build/services/dev-preview-servers/discover.service.js +106 -0
- package/build/services/dev-preview-servers/discover.service.js.map +1 -0
- package/build/services/native/build-info.service.d.ts +11 -0
- package/build/services/native/build-info.service.d.ts.map +1 -0
- package/build/services/native/build-info.service.js +18 -0
- package/build/services/native/build-info.service.js.map +1 -0
- package/build/services/native/native.service.d.ts +24 -0
- package/build/services/native/native.service.d.ts.map +1 -0
- package/build/services/native/native.service.js +13 -0
- package/build/services/native/native.service.js.map +1 -0
- package/build/services/previews/download.service.d.ts +14 -0
- package/build/services/previews/download.service.d.ts.map +1 -0
- package/build/services/previews/download.service.js +66 -0
- package/build/services/previews/download.service.js.map +1 -0
- package/build/services/previews/local-previews.service.d.ts +25 -0
- package/build/services/previews/local-previews.service.d.ts.map +1 -0
- package/build/services/previews/local-previews.service.js +50 -0
- package/build/services/previews/local-previews.service.js.map +1 -0
- package/build/services/previews/reload.service.d.ts +6 -0
- package/build/services/previews/reload.service.d.ts.map +1 -0
- package/build/services/previews/reload.service.js +13 -0
- package/build/services/previews/reload.service.js.map +1 -0
- package/build/services/previews/remote.service.d.ts +11 -0
- package/build/services/previews/remote.service.d.ts.map +1 -0
- package/build/services/previews/remote.service.js +34 -0
- package/build/services/previews/remote.service.js.map +1 -0
- package/build/services/previews/select.service.d.ts +11 -0
- package/build/services/previews/select.service.d.ts.map +1 -0
- package/build/services/previews/select.service.js +25 -0
- package/build/services/previews/select.service.js.map +1 -0
- package/build/services/previews/use-preview.service.d.ts +21 -0
- package/build/services/previews/use-preview.service.d.ts.map +1 -0
- package/build/services/previews/use-preview.service.js +24 -0
- package/build/services/previews/use-preview.service.js.map +1 -0
- package/build/services/previews/use-running.service.d.ts +4 -0
- package/build/services/previews/use-running.service.d.ts.map +1 -0
- package/build/services/previews/use-running.service.js +21 -0
- package/build/services/previews/use-running.service.js.map +1 -0
- package/build/services/publish/bundle.service.d.ts +11 -0
- package/build/services/publish/bundle.service.d.ts.map +1 -0
- package/build/services/publish/bundle.service.js +46 -0
- package/build/services/publish/bundle.service.js.map +1 -0
- package/build/services/publish/commands.service.d.ts +23 -0
- package/build/services/publish/commands.service.d.ts.map +1 -0
- package/build/services/publish/commands.service.js +130 -0
- package/build/services/publish/commands.service.js.map +1 -0
- package/build/services/publish/eas-env.service.d.ts +9 -0
- package/build/services/publish/eas-env.service.d.ts.map +1 -0
- package/build/services/publish/eas-env.service.js +49 -0
- package/build/services/publish/eas-env.service.js.map +1 -0
- package/build/services/registry/registry.service.d.ts +27 -0
- package/build/services/registry/registry.service.d.ts.map +1 -0
- package/build/services/registry/registry.service.js +89 -0
- package/build/services/registry/registry.service.js.map +1 -0
- package/build/services/s3/s3.service.d.ts +59 -0
- package/build/services/s3/s3.service.d.ts.map +1 -0
- package/build/services/s3/s3.service.js +154 -0
- package/build/services/s3/s3.service.js.map +1 -0
- package/build/services/start/start.service.d.ts +48 -0
- package/build/services/start/start.service.d.ts.map +1 -0
- package/build/services/start/start.service.js +172 -0
- package/build/services/start/start.service.js.map +1 -0
- package/build/stores/dev-preview-servers/dev-preview-servers.store.d.ts +21 -0
- package/build/stores/dev-preview-servers/dev-preview-servers.store.d.ts.map +1 -0
- package/build/stores/dev-preview-servers/dev-preview-servers.store.js +91 -0
- package/build/stores/dev-preview-servers/dev-preview-servers.store.js.map +1 -0
- package/build/stores/previews/previews.store.d.ts +36 -0
- package/build/stores/previews/previews.store.d.ts.map +1 -0
- package/build/stores/previews/previews.store.js +134 -0
- package/build/stores/previews/previews.store.js.map +1 -0
- package/build/utils/config/config.util.d.ts +39 -0
- package/build/utils/config/config.util.d.ts.map +1 -0
- package/build/utils/config/config.util.js +24 -0
- package/build/utils/config/config.util.js.map +1 -0
- package/build/utils/config/expo-config.util.d.ts +30 -0
- package/build/utils/config/expo-config.util.d.ts.map +1 -0
- package/build/utils/config/expo-config.util.js +43 -0
- package/build/utils/config/expo-config.util.js.map +1 -0
- package/build/utils/dev-preview-servers/merge-servers.util.d.ts +47 -0
- package/build/utils/dev-preview-servers/merge-servers.util.d.ts.map +1 -0
- package/build/utils/dev-preview-servers/merge-servers.util.js +37 -0
- package/build/utils/dev-preview-servers/merge-servers.util.js.map +1 -0
- package/build/utils/preview-list/theme.util.d.ts +5 -0
- package/build/utils/preview-list/theme.util.d.ts.map +1 -0
- package/build/utils/preview-list/theme.util.js +12 -0
- package/build/utils/preview-list/theme.util.js.map +1 -0
- package/build/utils/previews/layout.util.d.ts +89 -0
- package/build/utils/previews/layout.util.d.ts.map +1 -0
- package/build/utils/previews/layout.util.js +30 -0
- package/build/utils/previews/layout.util.js.map +1 -0
- package/build/utils/previews/preview-items.util.d.ts +23 -0
- package/build/utils/previews/preview-items.util.d.ts.map +1 -0
- package/build/utils/previews/preview-items.util.js +39 -0
- package/build/utils/previews/preview-items.util.js.map +1 -0
- package/build/utils/previews/preview-title.util.d.ts +2 -0
- package/build/utils/previews/preview-title.util.d.ts.map +1 -0
- package/build/utils/previews/preview-title.util.js +6 -0
- package/build/utils/previews/preview-title.util.js.map +1 -0
- package/build/utils/previews/running.util.d.ts +23 -0
- package/build/utils/previews/running.util.d.ts.map +1 -0
- package/build/utils/previews/running.util.js +23 -0
- package/build/utils/previews/running.util.js.map +1 -0
- package/build/utils/publish/changelog.util.d.ts +5 -0
- package/build/utils/publish/changelog.util.d.ts.map +1 -0
- package/build/utils/publish/changelog.util.js +19 -0
- package/build/utils/publish/changelog.util.js.map +1 -0
- package/build/utils/publish/indexes.util.d.ts +12 -0
- package/build/utils/publish/indexes.util.d.ts.map +1 -0
- package/build/utils/publish/indexes.util.js +52 -0
- package/build/utils/publish/indexes.util.js.map +1 -0
- package/build/utils/s3/sigv4.util.d.ts +24 -0
- package/build/utils/s3/sigv4.util.d.ts.map +1 -0
- package/build/utils/s3/sigv4.util.js +67 -0
- package/build/utils/s3/sigv4.util.js.map +1 -0
- package/expo-module.config.json +10 -0
- package/ios/AxonpackPreview.podspec +30 -0
- package/ios/AxonpackPreviewModule.swift +55 -0
- package/ios/PreviewReactDelegateHandler.swift +147 -0
- package/ios/PreviewState.swift +236 -0
- package/notes/README.md +36 -0
- package/notes/bundles.md +35 -0
- package/notes/dev-preview-servers.md +67 -0
- package/notes/distribution.md +138 -0
- package/notes/native-loader.md +56 -0
- package/notes/storage.md +145 -0
- package/notes/testing.md +97 -0
- package/package.json +107 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @axonpack/preview
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8005dc8: - **New package**: publish a JS bundle per branch or pull request, and switch to it in the installed app with no native rebuild
|
|
8
|
+
- **Preview list**: `PreviewProvider` and a ready-made list to download, switch, update and delete previews. `usePreview` and `useRunning` let you build your own screen
|
|
9
|
+
- **Crash fallback**: if a preview crashes before it starts, the next launch goes back to the built-in bundle
|
|
10
|
+
- **Only previews that fit**: matched by app version or native fingerprint with the config plugin's `compatibility` option. Downloads for another build show under Incompatible, with Delete all
|
|
11
|
+
- **`preview` CLI**: `publish`, `unpublish` and `prune` work with any S3-compatible bucket, public or private with a read-only key. `--environment` builds with an EAS environment's variables
|
|
12
|
+
- **Local Metro servers**: `preview start` runs Metro on a free port, and debug builds can switch between local servers from the list
|
|
13
|
+
- **expo-updates**: works alongside it
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Md Asadujjaman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,119 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="https://raw.githubusercontent.com/axonpack/axonpack/main/packages/@axonpack/expo-devtools/docs/logo.png" width="88" alt="Axonpack" />
|
|
4
|
+
|
|
5
|
+
# @axonpack/preview
|
|
6
|
+
|
|
7
|
+
**Hand QA the JS of every pull request, inside the build they already have.**
|
|
8
|
+
|
|
9
|
+
Publish one JS bundle per branch to your own S3-compatible bucket. QA opens a list in the app,
|
|
10
|
+
picks a preview, and the app restarts into it. No native rebuild, no new install, and the base
|
|
11
|
+
build comes back with one tap. Like EAS Update or CodePush, but only for previews.
|
|
12
|
+
|
|
13
|
+
**[Documentation](https://axonpack.dev/docs/preview)** · [Quick start](https://axonpack.dev/docs/preview/quick-start) · [CLI](https://axonpack.dev/docs/preview/cli) · [Changelog](https://axonpack.dev/docs/preview/changelog)
|
|
14
|
+
|
|
15
|
+
[](https://www.npmjs.com/package/@axonpack/preview)
|
|
16
|
+
[](https://www.npmjs.com/package/@axonpack/preview)
|
|
17
|
+
[](https://github.com/axonpack/axonpack/blob/main/LICENSE)
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npx expo install @axonpack/preview expo-file-system
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
It needs React Native's New Architecture, which means Expo SDK 53 or later. Under the old
|
|
28
|
+
architecture the package logs one warning and always loads the base bundle, so the app still
|
|
29
|
+
starts. It has native code, so it runs in a development or release build, not in Expo Go.
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
Add the config plugin to `app.json`, then run a prebuild or a new native build:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"expo": {
|
|
38
|
+
"plugins": [["@axonpack/preview", { "compatibility": "appVersion" }]]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The plugin bakes a key into the build. The app lists only previews published under that key.
|
|
44
|
+
`appVersion` (the default) keys on the app version; `fingerprint` keys on the native fingerprint.
|
|
45
|
+
|
|
46
|
+
Wrap the app in the provider and put the list on a screen QA can reach:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { PreviewList, PreviewProvider } from "@axonpack/preview";
|
|
50
|
+
|
|
51
|
+
export default function App() {
|
|
52
|
+
return (
|
|
53
|
+
<PreviewProvider
|
|
54
|
+
config={{
|
|
55
|
+
enabled: true,
|
|
56
|
+
url: process.env.EXPO_PUBLIC_PREVIEW_S3_URL ?? "",
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<PreviewList style={{ flex: 1 }} />
|
|
60
|
+
</PreviewProvider>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Publish the current branch from the app's root. The bucket and the write key come from the
|
|
66
|
+
environment, or from a gitignored `.env.local`:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
PREVIEW_S3_URL=https://<account>.r2.cloudflarestorage.com/previews \
|
|
70
|
+
PREVIEW_S3_ACCESS_KEY_ID=... PREVIEW_S3_SECRET_ACCESS_KEY=... \
|
|
71
|
+
npx preview publish
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The write key is for publishing only. Never put it in the app: the app gets the bucket's public
|
|
75
|
+
address, or a read-only key for a private bucket.
|
|
76
|
+
|
|
77
|
+
## Documentation
|
|
78
|
+
|
|
79
|
+
Everything else is on the docs site.
|
|
80
|
+
|
|
81
|
+
| Page | What is on it |
|
|
82
|
+
| ---------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
83
|
+
| [Quick start](https://axonpack.dev/docs/preview/quick-start) | From an empty bucket to switching previews in the app |
|
|
84
|
+
| [Storage](https://axonpack.dev/docs/preview/storage) | Public or private bucket, and the write and read keys |
|
|
85
|
+
| [AWS S3](https://axonpack.dev/docs/preview/aws-s3) | A bucket, a policy and both keys on AWS |
|
|
86
|
+
| [Cloudflare R2](https://axonpack.dev/docs/preview/cloudflare-r2) | A bucket, a public address and both keys on R2 |
|
|
87
|
+
| [MinIO](https://axonpack.dev/docs/preview/minio) | Your own server on a VPS, or the local one for trying it out |
|
|
88
|
+
| [Settings and env files](https://axonpack.dev/docs/preview/environment) | Where the publish settings and the app's settings come from, and EAS |
|
|
89
|
+
| [Publishing](https://axonpack.dev/docs/preview/publishing) | Publish, replace, unpublish and prune |
|
|
90
|
+
| [CI](https://axonpack.dev/docs/preview/ci) | Publish every pull request with GitHub Actions |
|
|
91
|
+
| [Compatibility](https://axonpack.dev/docs/preview/compatibility) | The config plugin, and app version or fingerprint |
|
|
92
|
+
| [App setup](https://axonpack.dev/docs/preview/app-setup) | The provider, the list, your own screen and a "what runs" banner |
|
|
93
|
+
| [Dev preview servers](https://axonpack.dev/docs/preview/dev-preview-servers) | Several Metro servers, one per worktree, switched live in a debug build |
|
|
94
|
+
| [expo-updates](https://axonpack.dev/docs/preview/expo-updates) | How previews sit on top of a build that expo-updates manages |
|
|
95
|
+
| [Troubleshooting](https://axonpack.dev/docs/preview/troubleshooting) | An empty list, a failed download, and what to check |
|
|
96
|
+
| [CLI](https://axonpack.dev/docs/preview/cli) | Every command and option |
|
|
97
|
+
| [Reference](https://axonpack.dev/docs/preview/reference) | Every export, config field, label and theme token |
|
|
98
|
+
| [Changelog](https://axonpack.dev/docs/preview/changelog) | Every published release, also in [`CHANGELOG.md`](./CHANGELOG.md) |
|
|
99
|
+
|
|
100
|
+
## Example app
|
|
101
|
+
|
|
102
|
+
`example/` is the default `create-expo-app` template with the list on a Previews tab and a banner
|
|
103
|
+
on every tab that says what runs. It publishes to a local MinIO, so it needs no cloud account:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
bun run storage:local # MinIO in Docker or Podman
|
|
107
|
+
cp example/.env.example example/.env
|
|
108
|
+
cp example/.env.local.example example/.env.local
|
|
109
|
+
cd example && npx expo run:ios # or: npx expo run:android
|
|
110
|
+
bun run publish:preview --id task-1-login-fix
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The whole run, with the device setup for each platform, is in
|
|
114
|
+
[the testing note](https://github.com/axonpack/axonpack/blob/main/packages/@axonpack/preview/notes/testing.md).
|
|
115
|
+
|
|
116
|
+
## What's built, and what isn't
|
|
117
|
+
|
|
118
|
+
See [the feature list](https://github.com/axonpack/axonpack/blob/main/packages/@axonpack/preview/notes/README.md) for what's built, what the platform genuinely can't do (and why this
|
|
119
|
+
doesn't fake it), and what's still on the table.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id 'com.android.library'
|
|
3
|
+
id 'expo-module-gradle-plugin'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
group = 'expo.modules.axonpackpreview'
|
|
7
|
+
version = '0.0.0'
|
|
8
|
+
|
|
9
|
+
android {
|
|
10
|
+
namespace "expo.modules.axonpackpreview"
|
|
11
|
+
defaultConfig {
|
|
12
|
+
versionCode 1
|
|
13
|
+
versionName '0.0.0'
|
|
14
|
+
consumerProguardFiles 'consumer-rules.pro'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
dependencies {
|
|
19
|
+
// ReactApplication and ReactHost, to restart into a newly chosen bundle.
|
|
20
|
+
implementation 'com.facebook.react:react-android'
|
|
21
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# @axonpack/preview reaches these by name to put a preview ahead of expo-updates (BundleLoaderOverride.kt).
|
|
2
|
+
-keepclassmembers class com.facebook.react.runtime.ReactHostImpl { *** reactHostDelegate; }
|
|
3
|
+
-keepclassmembers class expo.modules.ExpoReactHostFactory$ExpoReactHostDelegate { *** _jsBundleLoader; }
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package expo.modules.axonpackpreview
|
|
2
|
+
|
|
3
|
+
import android.content.pm.PackageManager
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import com.facebook.react.ReactApplication
|
|
6
|
+
import expo.modules.kotlin.modules.Module
|
|
7
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
8
|
+
|
|
9
|
+
class AxonpackPreviewModule : Module() {
|
|
10
|
+
override fun definition() = ModuleDefinition {
|
|
11
|
+
Name("AxonpackPreview")
|
|
12
|
+
|
|
13
|
+
Constants(
|
|
14
|
+
"compatibility" to metaData("COMPATIBILITY"),
|
|
15
|
+
"key" to metaData("KEY"),
|
|
16
|
+
"slug" to metaData("SLUG"),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
Function("select") { bundle: String?, key: String? ->
|
|
20
|
+
val context = appContext.reactContext ?: return@Function
|
|
21
|
+
PreviewState.select(context, bundle, key)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Function("selectLocalServer") { url: String?, hostPort: String? ->
|
|
25
|
+
val context = appContext.reactContext ?: return@Function
|
|
26
|
+
PreviewState.selectLocalServer(context, url, hostPort)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Function("state") {
|
|
30
|
+
appContext.reactContext?.let { PreviewState.read(it) } ?: emptyMap<String, Any?>()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Function("markLaunched") {
|
|
34
|
+
appContext.reactContext?.let { PreviewState.confirmStart(it) }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Function("reload") { reload() }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private fun reload() {
|
|
41
|
+
val app = appContext.reactContext?.applicationContext as? ReactApplication
|
|
42
|
+
if (app == null) {
|
|
43
|
+
Log.w(TAG, "The application is not a ReactApplication, so there is nothing to reload")
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
val host = app.reactHost
|
|
47
|
+
if (host == null || !PreviewState.newArchitecture()) {
|
|
48
|
+
PreviewState.warnOldArchitecture()
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
PreviewState.nextStart()
|
|
52
|
+
appContext.reactContext?.let {
|
|
53
|
+
PreviewState.pointDevSupport(it, host)
|
|
54
|
+
BundleLoaderOverride.apply(it, host)
|
|
55
|
+
}
|
|
56
|
+
host.reload("preview: switch bundle")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** What the config plugin wrote at prebuild; null when the plugin is not in the app config. */
|
|
60
|
+
private fun metaData(name: String): String? {
|
|
61
|
+
val context = appContext.reactContext ?: return null
|
|
62
|
+
return try {
|
|
63
|
+
context.packageManager
|
|
64
|
+
.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA)
|
|
65
|
+
.metaData
|
|
66
|
+
?.getString("dev.axonpack.preview.$name")
|
|
67
|
+
} catch (e: Exception) {
|
|
68
|
+
null
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package expo.modules.axonpackpreview
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import com.facebook.react.ReactHost
|
|
6
|
+
import com.facebook.react.bridge.JSBundleLoader
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* expo-updates is asked for the bundle before this package, and once it has applied a downloaded
|
|
10
|
+
* update it always answers. So with expo-updates installed, the selected preview is also put
|
|
11
|
+
* straight into the host, in the same private field expo-dev-launcher replaces. Clearing it hands the
|
|
12
|
+
* start back to expo-updates. If the field is not there (another SDK, a minified build without the
|
|
13
|
+
* keep rule), the base bundle loads and a warning says why. It never throws.
|
|
14
|
+
*/
|
|
15
|
+
internal object BundleLoaderOverride {
|
|
16
|
+
private val expoUpdatesInstalled: Boolean by lazy {
|
|
17
|
+
try {
|
|
18
|
+
Class.forName("expo.modules.updates.UpdatesPackage")
|
|
19
|
+
true
|
|
20
|
+
} catch (e: ClassNotFoundException) {
|
|
21
|
+
false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fun apply(context: Context, host: ReactHost?) {
|
|
26
|
+
if (!expoUpdatesInstalled || host == null) return
|
|
27
|
+
val bundle = PreviewState.bundleToLoad(context)
|
|
28
|
+
try {
|
|
29
|
+
val delegate = host.javaClass.getDeclaredField("reactHostDelegate")
|
|
30
|
+
.apply { isAccessible = true }
|
|
31
|
+
.get(host) ?: return
|
|
32
|
+
delegate.javaClass.getDeclaredField("_jsBundleLoader")
|
|
33
|
+
.apply { isAccessible = true }
|
|
34
|
+
.set(delegate, bundle?.let { JSBundleLoader.createFileLoader(it.absolutePath) })
|
|
35
|
+
} catch (e: Exception) {
|
|
36
|
+
if (bundle != null) {
|
|
37
|
+
Log.w(TAG, "Could not put the preview ahead of expo-updates, so the base bundle loads", e)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package expo.modules.axonpackpreview
|
|
2
|
+
|
|
3
|
+
import java.io.InputStream
|
|
4
|
+
|
|
5
|
+
/** Hermes bytecode starts with an 8-byte magic number, then its format version as a 32-bit int. */
|
|
6
|
+
internal object HermesHeader {
|
|
7
|
+
private val MAGIC = byteArrayOf(0xc6.toByte(), 0x1f, 0xbc.toByte(), 0x03, 0xc1.toByte(), 0x03, 0x19, 0x1f)
|
|
8
|
+
|
|
9
|
+
/** Null for anything that is not Hermes bytecode, such as a plain JS bundle. */
|
|
10
|
+
fun version(input: InputStream): Int? {
|
|
11
|
+
val header = ByteArray(12)
|
|
12
|
+
var read = 0
|
|
13
|
+
while (read < header.size) {
|
|
14
|
+
val n = input.read(header, read, header.size - read)
|
|
15
|
+
if (n < 0) return null
|
|
16
|
+
read += n
|
|
17
|
+
}
|
|
18
|
+
if (!header.copyOfRange(0, 8).contentEquals(MAGIC)) return null
|
|
19
|
+
return (header[8].toInt() and 0xff) or
|
|
20
|
+
((header[9].toInt() and 0xff) shl 8) or
|
|
21
|
+
((header[10].toInt() and 0xff) shl 16) or
|
|
22
|
+
((header[11].toInt() and 0xff) shl 24)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package expo.modules.axonpackpreview
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.os.Handler
|
|
7
|
+
import android.os.Looper
|
|
8
|
+
import android.util.Log
|
|
9
|
+
import com.facebook.react.ReactApplication
|
|
10
|
+
import expo.modules.core.interfaces.ApplicationLifecycleListener
|
|
11
|
+
import expo.modules.core.interfaces.Package
|
|
12
|
+
import expo.modules.core.interfaces.ReactNativeHostHandler
|
|
13
|
+
|
|
14
|
+
class PreviewPackage : Package {
|
|
15
|
+
override fun createReactNativeHostHandlers(context: Context): List<ReactNativeHostHandler> {
|
|
16
|
+
val appContext = context.applicationContext
|
|
17
|
+
return listOf(
|
|
18
|
+
object : ReactNativeHostHandler {
|
|
19
|
+
// Asked each time the React instance is created, so a reload picks up a new choice. Null
|
|
20
|
+
// leaves the start to expo-updates or the embedded bundle, exactly as without this package.
|
|
21
|
+
override fun getJSBundleFile(useDeveloperSupport: Boolean): String? {
|
|
22
|
+
// A local Metro server is loaded by dev support, not from a file.
|
|
23
|
+
if (PreviewState.localServerHostPort(appContext) != null) return null
|
|
24
|
+
return PreviewState.bundleToLoad(appContext)?.absolutePath
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Expo hands JS errors to the host handlers and only throws itself when there are none, so
|
|
28
|
+
// this handler decides. A preview that failed while starting goes back to the base bundle
|
|
29
|
+
// right away; anything else is thrown again, so the app fails exactly as it would without
|
|
30
|
+
// this package.
|
|
31
|
+
override fun onReactInstanceException(useDeveloperSupport: Boolean, exception: Exception) {
|
|
32
|
+
if (!PreviewState.failStart(appContext)) throw exception
|
|
33
|
+
val host = (appContext as? ReactApplication)?.reactHost
|
|
34
|
+
// React tears the host and its screens down after reporting the error, so a reload would
|
|
35
|
+
// leave nothing on screen. Starting the app's own activity again brings up a fresh one,
|
|
36
|
+
// and with the preview cleared that start is the base bundle.
|
|
37
|
+
Handler(Looper.getMainLooper()).post {
|
|
38
|
+
PreviewState.nextStart()
|
|
39
|
+
PreviewState.pointDevSupport(appContext, host)
|
|
40
|
+
BundleLoaderOverride.apply(appContext, host)
|
|
41
|
+
val launch = appContext.packageManager.getLaunchIntentForPackage(appContext.packageName)
|
|
42
|
+
if (launch != null) {
|
|
43
|
+
launch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
|
44
|
+
appContext.startActivity(launch)
|
|
45
|
+
} else {
|
|
46
|
+
host?.reload("preview: the preview failed while starting")
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override fun createApplicationLifecycleListeners(context: Context): List<ApplicationLifecycleListener> =
|
|
55
|
+
listOf(
|
|
56
|
+
object : ApplicationLifecycleListener {
|
|
57
|
+
// Runs before any activity starts React, so the first start already gets the preview. Only
|
|
58
|
+
// touches the host when a preview is selected, so an app without one starts as it always did.
|
|
59
|
+
override fun onCreate(application: Application) {
|
|
60
|
+
if (
|
|
61
|
+
PreviewState.localServerHostPort(application) == null &&
|
|
62
|
+
PreviewState.bundleToLoad(application) == null
|
|
63
|
+
) {
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
val host = (application as? ReactApplication)?.reactHost
|
|
68
|
+
PreviewState.pointDevSupport(application, host)
|
|
69
|
+
BundleLoaderOverride.apply(application, host)
|
|
70
|
+
} catch (e: Exception) {
|
|
71
|
+
Log.w(TAG, "Could not point the start at the selected preview, the base bundle loads", e)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
}
|