@renbostudios/edge-ota 0.3.0 → 0.4.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/README.md +313 -11
- package/dist/index.js +113 -4
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,26 +1,328 @@
|
|
|
1
1
|
# @renbostudios/edge-ota CLI
|
|
2
2
|
|
|
3
|
-
Zero-SDK, self-hostable OTA
|
|
3
|
+
Zero-SDK, self-hostable OTA update CLI for Expo React Native. Push signed JavaScript bundles to any EdgeOTA server.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @renbostudios/edge-ota
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+ and an Expo project with `expo-updates` installed.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 1. Log in to your EdgeOTA account
|
|
17
|
+
edge-ota login
|
|
18
|
+
|
|
19
|
+
# 2. Register your project and generate signing keys
|
|
20
|
+
edge-ota init
|
|
21
|
+
|
|
22
|
+
# 3. Rebuild your native app (REQUIRED — see below)
|
|
23
|
+
npx expo prebuild --clean
|
|
24
|
+
npx expo run:ios
|
|
25
|
+
npx expo run:android
|
|
26
|
+
|
|
27
|
+
# 4. Push an OTA update
|
|
28
|
+
edge-ota push
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
6
32
|
|
|
7
33
|
## Commands
|
|
8
34
|
|
|
35
|
+
### `edge-ota login`
|
|
36
|
+
|
|
37
|
+
Authenticate with your EdgeOTA account. Credentials are stored globally at `~/.config/edge-ota/config.json`.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
edge-ota login
|
|
41
|
+
edge-ota login --server https://my-server.com # custom server
|
|
9
42
|
```
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
edge-ota
|
|
14
|
-
|
|
15
|
-
|
|
43
|
+
|
|
44
|
+
You'll be prompted for your email and password. The session token is saved globally — no per-project `.env` files needed.
|
|
45
|
+
|
|
46
|
+
### `edge-ota logout`
|
|
47
|
+
|
|
48
|
+
Remove stored credentials.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
edge-ota logout
|
|
16
52
|
```
|
|
17
53
|
|
|
18
|
-
|
|
54
|
+
### `edge-ota init`
|
|
55
|
+
|
|
56
|
+
Register your project on the EdgeOTA server, generate an ECDSA P-256 signing key pair, and configure `app.json` with the correct update URL.
|
|
19
57
|
|
|
20
58
|
```bash
|
|
21
|
-
|
|
59
|
+
edge-ota init
|
|
60
|
+
edge-ota init --server https://my-server.com # override server
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**What it does:**
|
|
64
|
+
|
|
65
|
+
1. Fetches your existing projects from the server (if any)
|
|
66
|
+
2. Lets you select an existing project or create a new one
|
|
67
|
+
3. Generates an ECDSA P-256 key pair
|
|
68
|
+
4. Registers the project on the server with your public key
|
|
69
|
+
5. Saves the private key to `~/.config/edge-ota/keys/<projectId>.key`
|
|
70
|
+
6. Updates your `app.json` with:
|
|
71
|
+
- `expo.updates.url` — points to your EdgeOTA server
|
|
72
|
+
- `expo.updates.checkAutomatically` — set to `"ON_LOAD"`
|
|
73
|
+
- `expo.updates.fallbackToCacheTimeout` — set to `30000`
|
|
74
|
+
- `expo.updates.requestHeaders` — sets `expo-channel-name` to `"production"`
|
|
75
|
+
- `expo.extra.edgeOtaServer` — the server URL for manual override
|
|
76
|
+
- `expo.extra.edgeOtaPublicKey` — the generated public key
|
|
77
|
+
|
|
78
|
+
**After init, you MUST rebuild your native app.** The CLI will print a prominent warning with instructions. See the [Rebuild Required](#rebuild-required) section below.
|
|
79
|
+
|
|
80
|
+
### `edge-ota push`
|
|
81
|
+
|
|
82
|
+
Export your Expo bundle and publish an OTA update to the server.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
edge-ota push # default: all platforms, production channel
|
|
86
|
+
edge-ota push --channel staging # deploy to staging
|
|
87
|
+
edge-ota push --platform ios # iOS only
|
|
88
|
+
edge-ota push --platform android # Android only
|
|
89
|
+
edge-ota push --skip-export # skip expo export, use existing ./dist
|
|
90
|
+
edge-ota push --dry-run # build and sign, but don't upload
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**What it does:**
|
|
94
|
+
|
|
95
|
+
1. Runs `npx expo export` to create the JavaScript bundle (unless `--skip-export`)
|
|
96
|
+
2. Collects all assets from `./dist`
|
|
97
|
+
3. For each target platform (ios/android):
|
|
98
|
+
- Locates the platform-specific bundle
|
|
99
|
+
- Signs the payload with your ECDSA private key
|
|
100
|
+
- Uploads the bundle + signature to the server
|
|
101
|
+
4. The server verifies the signature before storing the update
|
|
102
|
+
|
|
103
|
+
**How it reads your config:**
|
|
104
|
+
|
|
105
|
+
The CLI reads everything from `app.json` — no flags required for standard deploys:
|
|
106
|
+
- **Server URL** — from `expo.updates.url` (or `expo.extra.edgeOtaServer`)
|
|
107
|
+
- **Project ID** — parsed from the updates URL
|
|
108
|
+
- **Runtime version** — from `expo.runtimeVersion` (string or policy object)
|
|
109
|
+
- **Private key** — from `~/.config/edge-ota/keys/<projectId>.key`
|
|
110
|
+
|
|
111
|
+
### `edge-ota status`
|
|
112
|
+
|
|
113
|
+
List recent deployments for the current project.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
edge-ota status
|
|
117
|
+
edge-ota status --limit 20 # show more releases
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `edge-ota keygen`
|
|
121
|
+
|
|
122
|
+
Generate a new ECDSA P-256 key pair and print it to stdout. Does not write any files.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
edge-ota keygen
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Useful for manual key rotation or generating keys for server-side signing.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Rebuild Required
|
|
133
|
+
|
|
134
|
+
After running `edge-ota init`, you **must** rebuild your native app. The `expo-updates` native module reads its configuration (server URL, runtime version) from the **native binary**, not from `app.json` at runtime.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Regenerate native projects with fresh config
|
|
138
|
+
npx expo prebuild --clean
|
|
139
|
+
|
|
140
|
+
# Rebuild
|
|
141
|
+
npx expo run:ios
|
|
142
|
+
npx expo run:android
|
|
143
|
+
|
|
144
|
+
# Or via EAS
|
|
145
|
+
eas build --profile production
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**If you skip this step**, the app will fail with:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
Failed to check for update
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The native module has no valid server URL to reach because it was never embedded in the binary.
|
|
155
|
+
|
|
156
|
+
You must also rebuild after any change to `expo.updates.url` or `expo.runtimeVersion` in `app.json`.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## How It Works
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
Developer Machine EdgeOTA Server User's Phone
|
|
164
|
+
───────────────── ────────────── ────────────
|
|
165
|
+
edge-ota push ─────────────► POST /api/updates
|
|
166
|
+
(ECDSA verified)
|
|
167
|
+
stores bundle
|
|
168
|
+
◄──────────── GET /api/updates
|
|
169
|
+
(expo-updates SDK)
|
|
170
|
+
────────────► multipart manifest
|
|
171
|
+
◄──────────── GET /api/assets/:hash
|
|
172
|
+
download bundle
|
|
173
|
+
reload app ✓
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
1. `edge-ota push` runs `expo export` and uploads the Hermes bundle + ECDSA signature to your server
|
|
177
|
+
2. The server verifies the ECDSA P-256 signature before storing anything
|
|
178
|
+
3. When users open the app, `expo-updates` checks your server for a new manifest
|
|
179
|
+
4. The server responds with a signed multipart manifest (Expo Updates Protocol v1)
|
|
180
|
+
5. `expo-updates` downloads and applies the update automatically
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## What `init` Writes to `app.json`
|
|
185
|
+
|
|
186
|
+
After running `edge-ota init`, your `app.json` will contain:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"expo": {
|
|
191
|
+
"updates": {
|
|
192
|
+
"url": "https://your-server.com/api/projects/<projectId>/updates",
|
|
193
|
+
"checkAutomatically": "ON_LOAD",
|
|
194
|
+
"fallbackToCacheTimeout": 30000,
|
|
195
|
+
"requestHeaders": {
|
|
196
|
+
"expo-channel-name": "production"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
"runtimeVersion": "1.0.0",
|
|
200
|
+
"extra": {
|
|
201
|
+
"edgeOtaServer": "https://your-server.com",
|
|
202
|
+
"edgeOtaPublicKey": "-----BEGIN PUBLIC KEY-----\n..."
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The `runtimeVersion` field is **not modified** by the CLI — it must already exist in your `app.json`. It supports both static strings and policy objects:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
// Static (recommended)
|
|
212
|
+
"runtimeVersion": "1.0.0"
|
|
213
|
+
|
|
214
|
+
// Policy-based (auto-increments with expo.version)
|
|
215
|
+
"runtimeVersion": { "policy": "appVersion" }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Global Storage
|
|
221
|
+
|
|
222
|
+
All credentials and keys are stored outside your project directory:
|
|
223
|
+
|
|
224
|
+
| File | Contents |
|
|
225
|
+
|---|---|
|
|
226
|
+
| `~/.config/edge-ota/config.json` | Auth token, email, default server URL |
|
|
227
|
+
| `~/.config/edge-ota/keys/<projectId>.key` | Private ECDSA signing key (per-project) |
|
|
228
|
+
| `~/.config/edge-ota/update-check.json` | Auto-update check timestamp |
|
|
229
|
+
|
|
230
|
+
Nothing sensitive is ever stored in your project repository.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Environment Variables
|
|
235
|
+
|
|
236
|
+
| Variable | Purpose |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `EDGE_OTA_TOKEN` | Override the auth token (useful for CI/CD) |
|
|
239
|
+
| `EDGE_OTA_UPDATING` | Internal — prevents recursive auto-updates |
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Auto-Update
|
|
244
|
+
|
|
245
|
+
The CLI checks for newer versions on npm every 15 minutes. If a newer version is found, it auto-installs globally. This check happens silently in the background and never blocks command execution.
|
|
246
|
+
|
|
247
|
+
To disable: set `EDGE_OTA_UPDATING=true` in your environment.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Security
|
|
252
|
+
|
|
253
|
+
- All bundles are signed with ECDSA P-256 before upload
|
|
254
|
+
- The server rejects any bundle with an invalid or missing signature
|
|
255
|
+
- Private keys are stored with `0600` permissions (owner read/write only)
|
|
256
|
+
- Session tokens expire after 24 hours
|
|
257
|
+
- Private keys never leave your machine
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Server Setup
|
|
262
|
+
|
|
263
|
+
The EdgeOTA server is a standard Node.js/Express app. You can self-host it or use the managed cloud service.
|
|
264
|
+
|
|
265
|
+
### Docker
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
docker pull renbostudios/edge-ota-server:latest
|
|
269
|
+
docker run -p 3000:3000 -e DATABASE_URL=... renbostudios/edge-ota-server
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Docker Compose
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
git clone https://github.com/renbostudios/edge-ota-cli
|
|
276
|
+
cd edge-ota
|
|
277
|
+
docker-compose up -d
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Manual
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
git clone https://github.com/renbostudios/edge-ota-cli
|
|
284
|
+
cd edge-ota
|
|
285
|
+
pnpm install
|
|
286
|
+
pnpm --filter @renbostudios/edge-ota-server-node dev
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Troubleshooting
|
|
292
|
+
|
|
293
|
+
### "Failed to check for update" on app launch
|
|
294
|
+
|
|
295
|
+
The `expo-updates` native module can't reach your server because the URL was never embedded in the binary. Fix: rebuild the native app after `edge-ota init`.
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npx expo prebuild --clean
|
|
299
|
+
npx expo run:ios
|
|
300
|
+
npx expo run:android
|
|
22
301
|
```
|
|
23
302
|
|
|
303
|
+
### Updates work on iOS but not Android
|
|
304
|
+
|
|
305
|
+
The Android native project may still have the default Expo Updates URL. Fix: regenerate and rebuild.
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
npx expo prebuild --clean
|
|
309
|
+
npx expo run:android
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Verify by checking `android/app/src/main/AndroidManifest.xml` for the `EXPO_UPDATE_URL` meta-data entry — it should point to your EdgeOTA server.
|
|
313
|
+
|
|
314
|
+
### Updates stopped working after a version bump
|
|
315
|
+
|
|
316
|
+
If `runtimeVersion` uses the `appVersion` policy, every version bump changes the runtime version. Push a new update after bumping, or switch to a static string.
|
|
317
|
+
|
|
318
|
+
### "No signing key found" error on push
|
|
319
|
+
|
|
320
|
+
Run `edge-ota init` to generate keys, or use `edge-ota keygen` to generate a new pair and register it manually.
|
|
321
|
+
|
|
24
322
|
---
|
|
25
323
|
|
|
26
|
-
by
|
|
324
|
+
## Built by Renbo Studios
|
|
325
|
+
|
|
326
|
+
[renbostudios.com](https://renbostudios.com) — Mobile App Development Studio
|
|
327
|
+
|
|
328
|
+
MIT License
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* edge-ota keygen — Generate a new ECDSA key pair (prints to stdout)
|
|
14
14
|
*/
|
|
15
15
|
import { Command } from "commander";
|
|
16
|
-
import { spawn } from "child_process";
|
|
16
|
+
import { execSync, spawn } from "child_process";
|
|
17
17
|
import fs from "fs";
|
|
18
18
|
import os from "os";
|
|
19
19
|
import path from "path";
|
|
@@ -154,6 +154,70 @@ try {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
catch { /* fallback */ }
|
|
157
|
+
// ─── Auto Update Check ────────────────────────────────────────────────────────
|
|
158
|
+
const UPDATE_CHECK_FILE = path.join(GLOBAL_CONFIG_DIR, "update-check.json");
|
|
159
|
+
function isNewerVersion(current, latest) {
|
|
160
|
+
const cParts = current.split(".").map(Number);
|
|
161
|
+
const lParts = latest.split(".").map(Number);
|
|
162
|
+
for (let i = 0; i < 3; i++) {
|
|
163
|
+
const cNum = cParts[i] || 0;
|
|
164
|
+
const lNum = lParts[i] || 0;
|
|
165
|
+
if (lNum > cNum)
|
|
166
|
+
return true;
|
|
167
|
+
if (lNum < cNum)
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
async function checkAndAutoUpdate() {
|
|
173
|
+
if (process.env.EDGE_OTA_UPDATING === "true")
|
|
174
|
+
return;
|
|
175
|
+
try {
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
if (fs.existsSync(UPDATE_CHECK_FILE)) {
|
|
178
|
+
try {
|
|
179
|
+
const cache = JSON.parse(fs.readFileSync(UPDATE_CHECK_FILE, "utf-8"));
|
|
180
|
+
// Only check npm once every 15 minutes to keep CLI commands extremely fast
|
|
181
|
+
if (now - (cache.lastChecked || 0) < 15 * 60 * 1000) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch { /* ignore */ }
|
|
186
|
+
}
|
|
187
|
+
// Save timestamp immediately to prevent concurrent requests
|
|
188
|
+
fs.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
189
|
+
fs.writeFileSync(UPDATE_CHECK_FILE, JSON.stringify({ lastChecked: now }));
|
|
190
|
+
// Fetch latest version from registry with a short 1s timeout
|
|
191
|
+
const controller = new AbortController();
|
|
192
|
+
const timerId = setTimeout(() => controller.abort(), 1000);
|
|
193
|
+
const res = await fetch("https://registry.npmjs.org/@renbostudios/edge-ota/latest", {
|
|
194
|
+
signal: controller.signal
|
|
195
|
+
});
|
|
196
|
+
clearTimeout(timerId);
|
|
197
|
+
if (!res.ok)
|
|
198
|
+
return;
|
|
199
|
+
const data = (await res.json());
|
|
200
|
+
const latestVersion = data.version;
|
|
201
|
+
if (latestVersion && isNewerVersion(packageVersion, latestVersion)) {
|
|
202
|
+
console.log(`\n ${c.yellow}${c.reset} New version of ${c.bold}edge-ota${c.reset} available: ${c.green}${latestVersion}${c.reset} (current: ${c.dim}${packageVersion}${c.reset})`);
|
|
203
|
+
console.log(` ${c.blue}➜${c.reset} Auto-updating globally...`);
|
|
204
|
+
try {
|
|
205
|
+
execSync("npm install -g @renbostudios/edge-ota", {
|
|
206
|
+
stdio: "inherit",
|
|
207
|
+
env: { ...process.env, EDGE_OTA_UPDATING: "true" }
|
|
208
|
+
});
|
|
209
|
+
console.log(` ${c.green}✓${c.reset} Successfully auto-updated to ${c.bold}${latestVersion}${c.reset}!\n`);
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
console.error(` ${c.red}✗${c.reset} Auto-update failed: ${err.message}`);
|
|
213
|
+
console.error(` Please update manually: ${c.cyan}npm install -g @renbostudios/edge-ota${c.reset}\n`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Fail silently so CLI works offline
|
|
219
|
+
}
|
|
220
|
+
}
|
|
157
221
|
// ─── Banner ───────────────────────────────────────────────────────────────────
|
|
158
222
|
function printBanner() {
|
|
159
223
|
const sep = `${c.dim}${"─".repeat(52)}${c.reset}`;
|
|
@@ -182,12 +246,32 @@ function readAppJson(cwd) {
|
|
|
182
246
|
}
|
|
183
247
|
const expo = data?.expo ?? {};
|
|
184
248
|
// ── Runtime version ──
|
|
185
|
-
|
|
249
|
+
let runtimeVersion = expo.runtimeVersion;
|
|
186
250
|
if (!runtimeVersion) {
|
|
187
251
|
console.error(` ${c.red}✗${c.reset} ${c.bold}expo.runtimeVersion${c.reset} is not set in app.json`);
|
|
188
252
|
console.error(` Add: ${c.dim}"runtimeVersion": "1.0.0"${c.reset} under the expo key.\n`);
|
|
189
253
|
process.exit(1);
|
|
190
254
|
}
|
|
255
|
+
// Resolve runtimeVersion object (e.g. policy helper) to string
|
|
256
|
+
if (typeof runtimeVersion === "object" && runtimeVersion !== null) {
|
|
257
|
+
const policy = runtimeVersion.policy;
|
|
258
|
+
if (policy === "appVersion") {
|
|
259
|
+
runtimeVersion = expo.version;
|
|
260
|
+
}
|
|
261
|
+
else if (policy === "sdkVersion") {
|
|
262
|
+
runtimeVersion = expo.sdkVersion;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
runtimeVersion = expo.version || expo.sdkVersion;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (!runtimeVersion || typeof runtimeVersion !== "string") {
|
|
269
|
+
console.error(` ${c.red}✗${c.reset} Could not resolve ${c.bold}expo.runtimeVersion${c.reset} to a valid string in app.json`);
|
|
270
|
+
if (typeof expo.runtimeVersion === "object") {
|
|
271
|
+
console.error(` Since you are using a policy, please ensure either ${c.bold}expo.version${c.reset} or ${c.bold}expo.sdkVersion${c.reset} is defined.\n`);
|
|
272
|
+
}
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
191
275
|
// ── Updates URL ──
|
|
192
276
|
const updatesUrl = expo?.updates?.url ?? "";
|
|
193
277
|
if (!updatesUrl) {
|
|
@@ -507,6 +591,7 @@ program
|
|
|
507
591
|
console.log(` ${c.green}✓${c.reset} Logged in as ${c.bold}${data.email}${c.reset}`);
|
|
508
592
|
console.log(` ${c.dim}credentials saved to ${GLOBAL_CONFIG_FILE}${c.reset}`);
|
|
509
593
|
console.log(sep + "\n");
|
|
594
|
+
process.exit(0);
|
|
510
595
|
}
|
|
511
596
|
catch (e) {
|
|
512
597
|
spin.fail(`Connection failed: ${e.message}`);
|
|
@@ -529,6 +614,7 @@ program
|
|
|
529
614
|
else {
|
|
530
615
|
console.log(` ${c.dim}Already logged out.${c.reset}\n`);
|
|
531
616
|
}
|
|
617
|
+
process.exit(0);
|
|
532
618
|
});
|
|
533
619
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
534
620
|
// edge-ota init
|
|
@@ -644,8 +730,27 @@ program
|
|
|
644
730
|
console.log(` project ${c.dim}${projectId}${c.reset}`);
|
|
645
731
|
console.log(` server ${c.dim}${serverUrl}${c.reset}`);
|
|
646
732
|
console.log(` key file ${c.dim}${path.join(GLOBAL_KEYS_DIR, `${projectId}.key`)}${c.reset}`);
|
|
733
|
+
console.log(sep);
|
|
734
|
+
console.log(`\n${c.bold}${c.yellow} ⚠ REBUILD REQUIRED${c.reset}`);
|
|
735
|
+
console.log(sep);
|
|
736
|
+
console.log(` ${c.bold}Your native app MUST be rebuilt for OTA updates${c.reset}`);
|
|
737
|
+
console.log(` ${c.bold}to work.${c.reset} The server URL is baked into the native`);
|
|
738
|
+
console.log(` binary at build time — not read from app.json.`);
|
|
739
|
+
console.log();
|
|
740
|
+
console.log(` Run these commands in order:`);
|
|
741
|
+
console.log();
|
|
742
|
+
console.log(` ${c.cyan}npx expo prebuild --clean${c.reset}`);
|
|
743
|
+
console.log(` ${c.cyan}npx expo run:ios${c.reset}`);
|
|
744
|
+
console.log(` ${c.cyan}npx expo run:android${c.reset}`);
|
|
745
|
+
console.log();
|
|
746
|
+
console.log(` Or via EAS:`);
|
|
747
|
+
console.log(` ${c.cyan}eas build --profile production${c.reset}`);
|
|
748
|
+
console.log();
|
|
749
|
+
console.log(` ${c.red}Skipping this WILL cause "Failed to check for${c.reset}`);
|
|
750
|
+
console.log(` ${c.red}update" errors on app launch.${c.reset}`);
|
|
647
751
|
console.log(sep + "\n");
|
|
648
|
-
console.log(`
|
|
752
|
+
console.log(` Then run ${c.cyan}edge-ota push${c.reset} to publish your first update.\n`);
|
|
753
|
+
process.exit(0);
|
|
649
754
|
}
|
|
650
755
|
});
|
|
651
756
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -805,6 +910,7 @@ program
|
|
|
805
910
|
console.log(`\n${sep}`);
|
|
806
911
|
console.log(` ${c.green}✓${c.reset} ${c.bold}done${c.reset} ${c.dim}update will be applied on next OTA sync${c.reset}`);
|
|
807
912
|
console.log(`${sep}\n`);
|
|
913
|
+
process.exit(0);
|
|
808
914
|
});
|
|
809
915
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
810
916
|
// edge-ota status
|
|
@@ -839,7 +945,7 @@ program
|
|
|
839
945
|
spin.stop();
|
|
840
946
|
if (!releases.length) {
|
|
841
947
|
console.log(` ${c.dim}No releases found.${c.reset}\n`);
|
|
842
|
-
|
|
948
|
+
process.exit(0);
|
|
843
949
|
}
|
|
844
950
|
const limit = parseInt(options.limit);
|
|
845
951
|
const rows = releases.slice(0, limit).map((r) => ({
|
|
@@ -850,6 +956,7 @@ program
|
|
|
850
956
|
Created: r.created_at ? new Date(r.created_at).toLocaleString() : "—",
|
|
851
957
|
}));
|
|
852
958
|
console.table(rows);
|
|
959
|
+
process.exit(0);
|
|
853
960
|
});
|
|
854
961
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
855
962
|
// edge-ota keygen
|
|
@@ -868,5 +975,7 @@ program
|
|
|
868
975
|
console.log(` ${c.bold}public key${c.reset} ${c.dim}paste into dashboard → Settings → General${c.reset}`);
|
|
869
976
|
console.log(sep);
|
|
870
977
|
console.log(keys.publicKey);
|
|
978
|
+
process.exit(0);
|
|
871
979
|
});
|
|
980
|
+
await checkAndAutoUpdate();
|
|
872
981
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@renbostudios/edge-ota",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"preferGlobal": true,
|
|
5
5
|
"description": "Zero-SDK, self-hostable OTA update CLI for Expo — push signed JS bundles to any EdgeOTA server",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "EdgeOTA Contributors",
|
|
8
|
-
"homepage": "https://github.com/renbostudios/edge-ota",
|
|
8
|
+
"homepage": "https://github.com/renbostudios/edge-ota-cli",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/renbostudios/edge-ota.git"
|
|
11
|
+
"url": "https://github.com/renbostudios/edge-ota-cli.git"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/renbostudios/edge-ota/issues"
|
|
14
|
+
"url": "https://github.com/renbostudios/edge-ota-cli/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"expo",
|