@renbostudios/edge-ota 0.3.1 → 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 +47 -3
- 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
|
@@ -246,12 +246,32 @@ function readAppJson(cwd) {
|
|
|
246
246
|
}
|
|
247
247
|
const expo = data?.expo ?? {};
|
|
248
248
|
// ── Runtime version ──
|
|
249
|
-
|
|
249
|
+
let runtimeVersion = expo.runtimeVersion;
|
|
250
250
|
if (!runtimeVersion) {
|
|
251
251
|
console.error(` ${c.red}✗${c.reset} ${c.bold}expo.runtimeVersion${c.reset} is not set in app.json`);
|
|
252
252
|
console.error(` Add: ${c.dim}"runtimeVersion": "1.0.0"${c.reset} under the expo key.\n`);
|
|
253
253
|
process.exit(1);
|
|
254
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
|
+
}
|
|
255
275
|
// ── Updates URL ──
|
|
256
276
|
const updatesUrl = expo?.updates?.url ?? "";
|
|
257
277
|
if (!updatesUrl) {
|
|
@@ -571,6 +591,7 @@ program
|
|
|
571
591
|
console.log(` ${c.green}✓${c.reset} Logged in as ${c.bold}${data.email}${c.reset}`);
|
|
572
592
|
console.log(` ${c.dim}credentials saved to ${GLOBAL_CONFIG_FILE}${c.reset}`);
|
|
573
593
|
console.log(sep + "\n");
|
|
594
|
+
process.exit(0);
|
|
574
595
|
}
|
|
575
596
|
catch (e) {
|
|
576
597
|
spin.fail(`Connection failed: ${e.message}`);
|
|
@@ -593,6 +614,7 @@ program
|
|
|
593
614
|
else {
|
|
594
615
|
console.log(` ${c.dim}Already logged out.${c.reset}\n`);
|
|
595
616
|
}
|
|
617
|
+
process.exit(0);
|
|
596
618
|
});
|
|
597
619
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
598
620
|
// edge-ota init
|
|
@@ -708,8 +730,27 @@ program
|
|
|
708
730
|
console.log(` project ${c.dim}${projectId}${c.reset}`);
|
|
709
731
|
console.log(` server ${c.dim}${serverUrl}${c.reset}`);
|
|
710
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}`);
|
|
711
751
|
console.log(sep + "\n");
|
|
712
|
-
console.log(`
|
|
752
|
+
console.log(` Then run ${c.cyan}edge-ota push${c.reset} to publish your first update.\n`);
|
|
753
|
+
process.exit(0);
|
|
713
754
|
}
|
|
714
755
|
});
|
|
715
756
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -869,6 +910,7 @@ program
|
|
|
869
910
|
console.log(`\n${sep}`);
|
|
870
911
|
console.log(` ${c.green}✓${c.reset} ${c.bold}done${c.reset} ${c.dim}update will be applied on next OTA sync${c.reset}`);
|
|
871
912
|
console.log(`${sep}\n`);
|
|
913
|
+
process.exit(0);
|
|
872
914
|
});
|
|
873
915
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
874
916
|
// edge-ota status
|
|
@@ -903,7 +945,7 @@ program
|
|
|
903
945
|
spin.stop();
|
|
904
946
|
if (!releases.length) {
|
|
905
947
|
console.log(` ${c.dim}No releases found.${c.reset}\n`);
|
|
906
|
-
|
|
948
|
+
process.exit(0);
|
|
907
949
|
}
|
|
908
950
|
const limit = parseInt(options.limit);
|
|
909
951
|
const rows = releases.slice(0, limit).map((r) => ({
|
|
@@ -914,6 +956,7 @@ program
|
|
|
914
956
|
Created: r.created_at ? new Date(r.created_at).toLocaleString() : "—",
|
|
915
957
|
}));
|
|
916
958
|
console.table(rows);
|
|
959
|
+
process.exit(0);
|
|
917
960
|
});
|
|
918
961
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
919
962
|
// edge-ota keygen
|
|
@@ -932,6 +975,7 @@ program
|
|
|
932
975
|
console.log(` ${c.bold}public key${c.reset} ${c.dim}paste into dashboard → Settings → General${c.reset}`);
|
|
933
976
|
console.log(sep);
|
|
934
977
|
console.log(keys.publicKey);
|
|
978
|
+
process.exit(0);
|
|
935
979
|
});
|
|
936
980
|
await checkAndAutoUpdate();
|
|
937
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",
|