|
| 1 | +[](https://www.npmjs.com/package/@cloud-push/cloud) |
| 2 | +[](https://www.npmjs.com/package/@cloud-push/expo) |
| 3 | +[](https://www.npmjs.com/package/@cloud-push/next) |
| 4 | +[](https://www.npmjs.com/package/@cloud-push/expo) |
| 5 | +[](https://www.npmjs.com/package/@cloud-push/next) |
| 6 | + |
| 7 | +## **Typescript Only, Zero kotlin, Zero Swift, Zero java, Zero Object-C** |
| 8 | + |
| 9 | +**OTA Update solution compatible with Expo Updates** |
| 10 | +→ Self-hosted update distribution system |
| 11 | + |
| 12 | +## 📚 Documentation |
| 13 | + |
| 14 | +You can find the full usage guide and API reference in the |
| 15 | +👉 [**Cloud Push Docs**](https://doyoonkim12345.github.io/cloud-push/) |
| 16 | + |
| 17 | +## 🚀 Motivation |
| 18 | + |
| 19 | +Expo projects are highly customized React Native projects. Because of this, available CodePush solutions are limited. |
| 20 | +This project, inspired by [`hot-updater`](https://github.com/gronxb/hot-updater), offers an alternative way to manage bundles using storage services like S3, Firebase, Supabase, etc. |
| 21 | +It follows [Expo Updates technical specs](https://docs.expo.dev/technical-specs/expo-updates-1/) and maintains compatibility with Expo Updates. |
| 22 | + |
| 23 | + |
| 24 | +## 🧪 Compatibility |
| 25 | + |
| 26 | +- ✅ Works with `expo run:android --variant release` |
| 27 | +- ✅ Works with `expo run:ios --configuration Release` |
| 28 | +- ✅ Compatible with **Expo Managed Workflow** |
| 29 | +## ✨ Key Features |
| 30 | + |
| 31 | +- 📡 Self-hosted deployment supported |
| 32 | +- 📦 Flexible storage & DB (S3, Supabase, Firebase, etc.) |
| 33 | +- 🔄 Compatible with Expo Updates APIs |
| 34 | +- 💾 Web-based bundle version dashboard |
| 35 | +- 🌐 Supports expo.dev environment variables (via EAS Secrets) |
| 36 | +- 🪟 Works on Windows |
| 37 | +- 🧪 EAS build supported |
| 38 | + |
| 39 | + |
| 40 | +## 🛠 Configuration Examples |
| 41 | + |
| 42 | +### Supabase |
| 43 | + |
| 44 | +```ts |
| 45 | +import { defineConfig } from "@cloud-push/cli"; |
| 46 | +import { SupabaseStorageClient, SupabaseDbClient } from "@cloud-push/cloud"; |
| 47 | + |
| 48 | +export default defineConfig(() => ({ |
| 49 | + loadClients: () => { |
| 50 | + |
| 51 | + const storageClient = new SupabaseStorageClient({ |
| 52 | + bucketName: process.env.SUPABASE_BUCKET_NAME!, |
| 53 | + supabaseUrl: process.env.SUPABASE_URL!, |
| 54 | + supabaseKey: process.env.SUPABASE_KEY!, |
| 55 | + }); |
| 56 | + |
| 57 | + const dbClient = new SupabaseDbClient({ |
| 58 | + tableName: process.env.SUPABASE_TABLE_NAME!, |
| 59 | + supabaseUrl: process.env.SUPABASE_URL!, |
| 60 | + supabaseKey: process.env.SUPABASE_KEY!, |
| 61 | + }); |
| 62 | + |
| 63 | + return { |
| 64 | + storage: storageClient, |
| 65 | + db: dbClient, |
| 66 | + }; |
| 67 | + }, |
| 68 | +})); |
| 69 | +``` |
| 70 | + |
| 71 | +### AWS S3 + lowdb |
| 72 | + |
| 73 | +```ts |
| 74 | +import { defineConfig } from "@cloud-push/cli"; |
| 75 | +import { AWSS3StorageClient, LowDbClient } from "@cloud-push/cloud"; |
| 76 | + |
| 77 | +export default defineConfig(() => ({ |
| 78 | + loadClients: () => { |
| 79 | + |
| 80 | + const storageClient = new AWSS3StorageClient({ |
| 81 | + accessKeyId: process.env.AWS_ACCESS_KEY_ID!, |
| 82 | + bucketName: process.env.AWS_BUCKET_NAME!, |
| 83 | + region: process.env.AWS_REGION!, |
| 84 | + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, |
| 85 | + }); |
| 86 | + |
| 87 | + const dbClient = new LowDbClient({ |
| 88 | + downloadJSONFile: () => storageClient.getFile({ key: "cursor.json" }), |
| 89 | + uploadJSONFile: (file: Uint8Array) => |
| 90 | + storageClient.uploadFile({ key: "cursor.json", file }), |
| 91 | + }); |
| 92 | + |
| 93 | + return { |
| 94 | + storage: storageClient, |
| 95 | + db: dbClient, |
| 96 | + }; |
| 97 | + }, |
| 98 | +})); |
| 99 | +``` |
| 100 | + |
| 101 | +### Firebase |
| 102 | + |
| 103 | +```ts |
| 104 | +import { defineConfig } from "@cloud-push/cli"; |
| 105 | +import { FirebaseStorageClient, FirebaseDbClient } from "@cloud-push/cloud"; |
| 106 | + |
| 107 | +export default defineConfig(() => ({ |
| 108 | + loadClients: () => { |
| 109 | + const storageClient = new FirebaseStorageClient({ |
| 110 | + credential: process.env.FIREBASE_CREDENTIAL!, |
| 111 | + bucketName: process.env.BUCKET_NAME!, |
| 112 | + }); |
| 113 | + |
| 114 | + const dbClient = new FirebaseDbClient({ |
| 115 | + credential: process.env.FIREBASE_CREDENTIAL!, |
| 116 | + databaseId: process.env.FIREBASE_DATABASE_ID!, |
| 117 | + }); |
| 118 | + |
| 119 | + return { |
| 120 | + storage: storageClient, |
| 121 | + db: dbClient, |
| 122 | + }; |
| 123 | + }, |
| 124 | +})); |
| 125 | +``` |
| 126 | + |
| 127 | +## 📘 Expo Updates SDK Compatibility |
| 128 | + |
| 129 | +### 🧱 Constants |
| 130 | + |
| 131 | +| Constant | Supported | |
| 132 | +|------------------------------|-----------| |
| 133 | +| `Updates.channel` | ✅ | |
| 134 | +| `Updates.checkAutomatically` | ✅ | |
| 135 | +| `Updates.createdAt` | ✅ | |
| 136 | +| `Updates.emergencyLaunchReason` | ⏳ | |
| 137 | +| `Updates.isEmbeddedLaunch` | ✅ | |
| 138 | +| `Updates.isEmergencyLaunch` | ⏳ | |
| 139 | +| `Updates.isEnabled` | ✅ | |
| 140 | +| `Updates.latestContext` | ✅ | |
| 141 | +| `Updates.launchDuration` | ✅ | |
| 142 | +| `Updates.manifest` | ✅ | |
| 143 | +| `Updates.runtimeVersion` | ✅ | |
| 144 | +| `Updates.updateId` | ✅ | |
| 145 | + |
| 146 | +### 🧩 Hooks |
| 147 | + |
| 148 | +| Hook | Supported | |
| 149 | +|----------------|-----------| |
| 150 | +| `useUpdates()` | ✅ | |
| 151 | + |
| 152 | +### 🛠 Methods |
| 153 | + |
| 154 | +| Method | Supported | |
| 155 | +|-------------------------|-----------| |
| 156 | +| `checkForUpdateAsync()` | ✅ | |
| 157 | +| `clearLogEntriesAsync()`| ✅ | |
| 158 | +| `fetchUpdateAsync()` | ✅ | |
| 159 | +| `getExtraParamsAsync()` | ❌ | |
| 160 | +| `readLogEntriesAsync()` | ✅ | |
| 161 | +| `reloadAsync()` | ✅ | |
| 162 | +| `setExtraParamAsync()` | ❌ | |
| 163 | + |
| 164 | +--- |
0 commit comments