@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.
Files changed (182) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +118 -2
  4. package/android/build.gradle +21 -0
  5. package/android/consumer-rules.pro +3 -0
  6. package/android/src/main/java/expo/modules/axonpackpreview/AxonpackPreviewModule.kt +72 -0
  7. package/android/src/main/java/expo/modules/axonpackpreview/BundleLoaderOverride.kt +41 -0
  8. package/android/src/main/java/expo/modules/axonpackpreview/HermesHeader.kt +24 -0
  9. package/android/src/main/java/expo/modules/axonpackpreview/PreviewPackage.kt +76 -0
  10. package/android/src/main/java/expo/modules/axonpackpreview/PreviewState.kt +295 -0
  11. package/app.plugin.js +112 -0
  12. package/bin/preview.js +2 -0
  13. package/build/cli.cjs +1465 -0
  14. package/build/cli.d.ts +3 -0
  15. package/build/cli.d.ts.map +1 -0
  16. package/build/cli.js +164 -0
  17. package/build/cli.js.map +1 -0
  18. package/build/components/preview-list/preview-list.component.d.ts +13 -0
  19. package/build/components/preview-list/preview-list.component.d.ts.map +1 -0
  20. package/build/components/preview-list/preview-list.component.js +316 -0
  21. package/build/components/preview-list/preview-list.component.js.map +1 -0
  22. package/build/components/preview-list/preview-list.types.d.ts +59 -0
  23. package/build/components/preview-list/preview-list.types.d.ts.map +1 -0
  24. package/build/components/preview-list/preview-list.types.js +2 -0
  25. package/build/components/preview-list/preview-list.types.js.map +1 -0
  26. package/build/components/preview-provider/preview-provider.component.d.ts +12 -0
  27. package/build/components/preview-provider/preview-provider.component.d.ts.map +1 -0
  28. package/build/components/preview-provider/preview-provider.component.js +19 -0
  29. package/build/components/preview-provider/preview-provider.component.js.map +1 -0
  30. package/build/constants/dev-preview-servers/dev-preview-servers.const.d.ts +26 -0
  31. package/build/constants/dev-preview-servers/dev-preview-servers.const.d.ts.map +1 -0
  32. package/build/constants/dev-preview-servers/dev-preview-servers.const.js +9 -0
  33. package/build/constants/dev-preview-servers/dev-preview-servers.const.js.map +1 -0
  34. package/build/constants/preview-list/theme.const.d.ts +5 -0
  35. package/build/constants/preview-list/theme.const.d.ts.map +1 -0
  36. package/build/constants/preview-list/theme.const.js +65 -0
  37. package/build/constants/preview-list/theme.const.js.map +1 -0
  38. package/build/index.d.ts +24 -0
  39. package/build/index.d.ts.map +1 -0
  40. package/build/index.js +16 -0
  41. package/build/index.js.map +1 -0
  42. package/build/services/cli/config.service.d.ts +13 -0
  43. package/build/services/cli/config.service.d.ts.map +1 -0
  44. package/build/services/cli/config.service.js +39 -0
  45. package/build/services/cli/config.service.js.map +1 -0
  46. package/build/services/cli/project.service.d.ts +25 -0
  47. package/build/services/cli/project.service.d.ts.map +1 -0
  48. package/build/services/cli/project.service.js +81 -0
  49. package/build/services/cli/project.service.js.map +1 -0
  50. package/build/services/cli/title.service.d.ts +15 -0
  51. package/build/services/cli/title.service.d.ts.map +1 -0
  52. package/build/services/cli/title.service.js +24 -0
  53. package/build/services/cli/title.service.js.map +1 -0
  54. package/build/services/dev-preview-servers/discover.service.d.ts +14 -0
  55. package/build/services/dev-preview-servers/discover.service.d.ts.map +1 -0
  56. package/build/services/dev-preview-servers/discover.service.js +106 -0
  57. package/build/services/dev-preview-servers/discover.service.js.map +1 -0
  58. package/build/services/native/build-info.service.d.ts +11 -0
  59. package/build/services/native/build-info.service.d.ts.map +1 -0
  60. package/build/services/native/build-info.service.js +18 -0
  61. package/build/services/native/build-info.service.js.map +1 -0
  62. package/build/services/native/native.service.d.ts +24 -0
  63. package/build/services/native/native.service.d.ts.map +1 -0
  64. package/build/services/native/native.service.js +13 -0
  65. package/build/services/native/native.service.js.map +1 -0
  66. package/build/services/previews/download.service.d.ts +14 -0
  67. package/build/services/previews/download.service.d.ts.map +1 -0
  68. package/build/services/previews/download.service.js +66 -0
  69. package/build/services/previews/download.service.js.map +1 -0
  70. package/build/services/previews/local-previews.service.d.ts +25 -0
  71. package/build/services/previews/local-previews.service.d.ts.map +1 -0
  72. package/build/services/previews/local-previews.service.js +50 -0
  73. package/build/services/previews/local-previews.service.js.map +1 -0
  74. package/build/services/previews/reload.service.d.ts +6 -0
  75. package/build/services/previews/reload.service.d.ts.map +1 -0
  76. package/build/services/previews/reload.service.js +13 -0
  77. package/build/services/previews/reload.service.js.map +1 -0
  78. package/build/services/previews/remote.service.d.ts +11 -0
  79. package/build/services/previews/remote.service.d.ts.map +1 -0
  80. package/build/services/previews/remote.service.js +34 -0
  81. package/build/services/previews/remote.service.js.map +1 -0
  82. package/build/services/previews/select.service.d.ts +11 -0
  83. package/build/services/previews/select.service.d.ts.map +1 -0
  84. package/build/services/previews/select.service.js +25 -0
  85. package/build/services/previews/select.service.js.map +1 -0
  86. package/build/services/previews/use-preview.service.d.ts +21 -0
  87. package/build/services/previews/use-preview.service.d.ts.map +1 -0
  88. package/build/services/previews/use-preview.service.js +24 -0
  89. package/build/services/previews/use-preview.service.js.map +1 -0
  90. package/build/services/previews/use-running.service.d.ts +4 -0
  91. package/build/services/previews/use-running.service.d.ts.map +1 -0
  92. package/build/services/previews/use-running.service.js +21 -0
  93. package/build/services/previews/use-running.service.js.map +1 -0
  94. package/build/services/publish/bundle.service.d.ts +11 -0
  95. package/build/services/publish/bundle.service.d.ts.map +1 -0
  96. package/build/services/publish/bundle.service.js +46 -0
  97. package/build/services/publish/bundle.service.js.map +1 -0
  98. package/build/services/publish/commands.service.d.ts +23 -0
  99. package/build/services/publish/commands.service.d.ts.map +1 -0
  100. package/build/services/publish/commands.service.js +130 -0
  101. package/build/services/publish/commands.service.js.map +1 -0
  102. package/build/services/publish/eas-env.service.d.ts +9 -0
  103. package/build/services/publish/eas-env.service.d.ts.map +1 -0
  104. package/build/services/publish/eas-env.service.js +49 -0
  105. package/build/services/publish/eas-env.service.js.map +1 -0
  106. package/build/services/registry/registry.service.d.ts +27 -0
  107. package/build/services/registry/registry.service.d.ts.map +1 -0
  108. package/build/services/registry/registry.service.js +89 -0
  109. package/build/services/registry/registry.service.js.map +1 -0
  110. package/build/services/s3/s3.service.d.ts +59 -0
  111. package/build/services/s3/s3.service.d.ts.map +1 -0
  112. package/build/services/s3/s3.service.js +154 -0
  113. package/build/services/s3/s3.service.js.map +1 -0
  114. package/build/services/start/start.service.d.ts +48 -0
  115. package/build/services/start/start.service.d.ts.map +1 -0
  116. package/build/services/start/start.service.js +172 -0
  117. package/build/services/start/start.service.js.map +1 -0
  118. package/build/stores/dev-preview-servers/dev-preview-servers.store.d.ts +21 -0
  119. package/build/stores/dev-preview-servers/dev-preview-servers.store.d.ts.map +1 -0
  120. package/build/stores/dev-preview-servers/dev-preview-servers.store.js +91 -0
  121. package/build/stores/dev-preview-servers/dev-preview-servers.store.js.map +1 -0
  122. package/build/stores/previews/previews.store.d.ts +36 -0
  123. package/build/stores/previews/previews.store.d.ts.map +1 -0
  124. package/build/stores/previews/previews.store.js +134 -0
  125. package/build/stores/previews/previews.store.js.map +1 -0
  126. package/build/utils/config/config.util.d.ts +39 -0
  127. package/build/utils/config/config.util.d.ts.map +1 -0
  128. package/build/utils/config/config.util.js +24 -0
  129. package/build/utils/config/config.util.js.map +1 -0
  130. package/build/utils/config/expo-config.util.d.ts +30 -0
  131. package/build/utils/config/expo-config.util.d.ts.map +1 -0
  132. package/build/utils/config/expo-config.util.js +43 -0
  133. package/build/utils/config/expo-config.util.js.map +1 -0
  134. package/build/utils/dev-preview-servers/merge-servers.util.d.ts +47 -0
  135. package/build/utils/dev-preview-servers/merge-servers.util.d.ts.map +1 -0
  136. package/build/utils/dev-preview-servers/merge-servers.util.js +37 -0
  137. package/build/utils/dev-preview-servers/merge-servers.util.js.map +1 -0
  138. package/build/utils/preview-list/theme.util.d.ts +5 -0
  139. package/build/utils/preview-list/theme.util.d.ts.map +1 -0
  140. package/build/utils/preview-list/theme.util.js +12 -0
  141. package/build/utils/preview-list/theme.util.js.map +1 -0
  142. package/build/utils/previews/layout.util.d.ts +89 -0
  143. package/build/utils/previews/layout.util.d.ts.map +1 -0
  144. package/build/utils/previews/layout.util.js +30 -0
  145. package/build/utils/previews/layout.util.js.map +1 -0
  146. package/build/utils/previews/preview-items.util.d.ts +23 -0
  147. package/build/utils/previews/preview-items.util.d.ts.map +1 -0
  148. package/build/utils/previews/preview-items.util.js +39 -0
  149. package/build/utils/previews/preview-items.util.js.map +1 -0
  150. package/build/utils/previews/preview-title.util.d.ts +2 -0
  151. package/build/utils/previews/preview-title.util.d.ts.map +1 -0
  152. package/build/utils/previews/preview-title.util.js +6 -0
  153. package/build/utils/previews/preview-title.util.js.map +1 -0
  154. package/build/utils/previews/running.util.d.ts +23 -0
  155. package/build/utils/previews/running.util.d.ts.map +1 -0
  156. package/build/utils/previews/running.util.js +23 -0
  157. package/build/utils/previews/running.util.js.map +1 -0
  158. package/build/utils/publish/changelog.util.d.ts +5 -0
  159. package/build/utils/publish/changelog.util.d.ts.map +1 -0
  160. package/build/utils/publish/changelog.util.js +19 -0
  161. package/build/utils/publish/changelog.util.js.map +1 -0
  162. package/build/utils/publish/indexes.util.d.ts +12 -0
  163. package/build/utils/publish/indexes.util.d.ts.map +1 -0
  164. package/build/utils/publish/indexes.util.js +52 -0
  165. package/build/utils/publish/indexes.util.js.map +1 -0
  166. package/build/utils/s3/sigv4.util.d.ts +24 -0
  167. package/build/utils/s3/sigv4.util.d.ts.map +1 -0
  168. package/build/utils/s3/sigv4.util.js +67 -0
  169. package/build/utils/s3/sigv4.util.js.map +1 -0
  170. package/expo-module.config.json +10 -0
  171. package/ios/AxonpackPreview.podspec +30 -0
  172. package/ios/AxonpackPreviewModule.swift +55 -0
  173. package/ios/PreviewReactDelegateHandler.swift +147 -0
  174. package/ios/PreviewState.swift +236 -0
  175. package/notes/README.md +36 -0
  176. package/notes/bundles.md +35 -0
  177. package/notes/dev-preview-servers.md +67 -0
  178. package/notes/distribution.md +138 -0
  179. package/notes/native-loader.md +56 -0
  180. package/notes/storage.md +145 -0
  181. package/notes/testing.md +97 -0
  182. 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
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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
+ [![npm version](https://img.shields.io/npm/v/@axonpack/preview.svg)](https://www.npmjs.com/package/@axonpack/preview)
16
+ [![npm downloads](https://img.shields.io/npm/dm/@axonpack/preview.svg)](https://www.npmjs.com/package/@axonpack/preview)
17
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ }