@renbostudios/edge-ota 0.2.0 → 0.2.2
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 +9 -6
- package/dist/index.js +97 -47
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# `@
|
|
1
|
+
# `@edge-ota/cli`
|
|
2
2
|
|
|
3
3
|
> Zero-SDK, self-hostable OTA update CLI for Expo apps.
|
|
4
4
|
> Push cryptographically signed JS bundles to any EdgeOTA server — no Expo account required.
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
npm install -g @
|
|
9
|
+
npm install -g @edge-ota/cli
|
|
10
10
|
# or use without installing:
|
|
11
|
-
npx @
|
|
11
|
+
npx @edge-ota/cli <command>
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
@@ -32,16 +32,17 @@ edge-ota status
|
|
|
32
32
|
|
|
33
33
|
Generates an ECDSA P-256 key pair, writes `edge-ota.config.json` and `.edge-ota.private.key`, auto-adds the private key to `.gitignore`, and automatically updates your Expo `app.json` configuration file with the new updates URL.
|
|
34
34
|
|
|
35
|
-
If
|
|
35
|
+
If `--server` is not specified, you will be prompted interactively to enter the URL of your hosted or local EdgeOTA server and your optional Project ID.
|
|
36
36
|
|
|
37
37
|
| Option | Default | Description |
|
|
38
38
|
|--------|---------|-------------|
|
|
39
39
|
| `-s, --server <url>` | — | Your EdgeOTA server URL (prompts if omitted) |
|
|
40
|
+
| `--project <id>` | — | Your EdgeOTA Project ID (prompts if omitted, optional) |
|
|
40
41
|
|
|
41
42
|
**Output:**
|
|
42
|
-
- `edge-ota.config.json` — server URL
|
|
43
|
+
- `edge-ota.config.json` — server URL, public key, and optional project ID (safe to commit)
|
|
43
44
|
- `.edge-ota.private.key` — private signing key (**never commit this**)
|
|
44
|
-
- **Auto-configured:** `app.json` is modified to point `expo.updates.url` to your server's `/api/updates` endpoint.
|
|
45
|
+
- **Auto-configured:** `app.json` is modified to point `expo.updates.url` to your server's updates endpoint. If a Project ID is configured, it points to `/api/projects/:projectId/updates`; otherwise, it falls back to the global `/api/updates` endpoint.
|
|
45
46
|
|
|
46
47
|
---
|
|
47
48
|
|
|
@@ -54,6 +55,7 @@ Runs `expo export`, hashes every asset with SHA-256, signs the payload with your
|
|
|
54
55
|
| `-c, --channel <name>` | `production` | Deployment channel |
|
|
55
56
|
| `-r, --runtime <version>` | `1.0.0` | Native runtime version (must match `runtimeVersion` in app.json) |
|
|
56
57
|
| `-p, --platform <platform>` | `all` | `ios`, `android`, or `all` |
|
|
58
|
+
| `--project <id>` | — | Project ID (overrides value in `edge-ota.config.json`) |
|
|
57
59
|
| `--skip-export` | — | Skip `expo export` — use existing `./dist` |
|
|
58
60
|
| `--dry-run` | — | Sign the payload but don't upload |
|
|
59
61
|
|
|
@@ -75,6 +77,7 @@ Lists recent releases from the server as a table.
|
|
|
75
77
|
|--------|---------|-------------|
|
|
76
78
|
| `-t, --token <token>` | `$EDGE_OTA_TOKEN` | Auth token from the dashboard |
|
|
77
79
|
| `-n, --limit <n>` | `10` | Number of releases to show |
|
|
80
|
+
| `--project <id>` | — | Project ID to query (overrides config) |
|
|
78
81
|
|
|
79
82
|
```bash
|
|
80
83
|
export EDGE_OTA_TOKEN="your-dashboard-token"
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,35 @@ import { createWriteStream } from "fs";
|
|
|
32
32
|
import archiver from "archiver";
|
|
33
33
|
import readline from "readline";
|
|
34
34
|
import { generateECDSAKeyPair, signPayload, sha256Hex } from "./core/index.js";
|
|
35
|
+
// ─── ANSI Styling Helpers ───────────────────────────────────────────────────
|
|
36
|
+
const colors = {
|
|
37
|
+
reset: "\x1b[0m",
|
|
38
|
+
bold: "\x1b[1m",
|
|
39
|
+
dim: "\x1b[2m",
|
|
40
|
+
underline: "\x1b[4m",
|
|
41
|
+
// Foreground Colors
|
|
42
|
+
black: "\x1b[30m",
|
|
43
|
+
red: "\x1b[31m",
|
|
44
|
+
green: "\x1b[32m",
|
|
45
|
+
yellow: "\x1b[33m",
|
|
46
|
+
blue: "\x1b[34m",
|
|
47
|
+
magenta: "\x1b[35m",
|
|
48
|
+
cyan: "\x1b[36m",
|
|
49
|
+
white: "\x1b[37m",
|
|
50
|
+
gray: "\x1b[90m",
|
|
51
|
+
};
|
|
52
|
+
function printBanner() {
|
|
53
|
+
console.log(`\n${colors.bold}${colors.cyan} ┌──────────────────────────────────────────────────┐`);
|
|
54
|
+
console.log(` │ ${colors.magenta}__ _ _ _ _ _ _ _ _ _ _ _ _ ${colors.cyan}│`);
|
|
55
|
+
console.log(` │${colors.bold}${colors.white} ███████╗██████╗ ██████╗ ███████╗ ██████╗ ████████╗ █████╗ ${colors.cyan}│`);
|
|
56
|
+
console.log(` │${colors.bold}${colors.white} ██╔════╝██╔══██╗██╔════╝ ██╔════╝██╔═══██╗╚══██╔══╝██╔══██╗ ${colors.cyan}│`);
|
|
57
|
+
console.log(` │${colors.bold}${colors.white} █████╗ ██║ ██║██║ ███╗█████╗ ██║ ██║ ██║ ███████║ ${colors.cyan}│`);
|
|
58
|
+
console.log(` │${colors.bold}${colors.white} ██╔══╝ ██║ ██║██║ ██║██╔══╝ ██║ ██║ ██║ ██╔══██║ ${colors.cyan}│`);
|
|
59
|
+
console.log(` │${colors.bold}${colors.white} ███████╗██████╔╝╚██████╔╝███████╗╚██████╔╝ ██║ ██║ ██║ ${colors.cyan}│`);
|
|
60
|
+
console.log(` │${colors.bold}${colors.white} ╚══════╝╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ${colors.cyan}│`);
|
|
61
|
+
console.log(` │ ${colors.gray}Zero-SDK OTA Update Platform — By Renbo Studios ${colors.cyan}│`);
|
|
62
|
+
console.log(` └──────────────────────────────────────────────────┘${colors.reset}\n`);
|
|
63
|
+
}
|
|
35
64
|
function askQuestion(query) {
|
|
36
65
|
const rl = readline.createInterface({
|
|
37
66
|
input: process.stdin,
|
|
@@ -44,7 +73,7 @@ function askQuestion(query) {
|
|
|
44
73
|
});
|
|
45
74
|
});
|
|
46
75
|
}
|
|
47
|
-
function updateAppJson(cwd, serverUrl) {
|
|
76
|
+
function updateAppJson(cwd, serverUrl, projectId) {
|
|
48
77
|
const appJsonPath = path.resolve(cwd, "app.json");
|
|
49
78
|
if (!fs.existsSync(appJsonPath)) {
|
|
50
79
|
console.log("⚠️ app.json not found in this directory. Skipping auto-update.");
|
|
@@ -59,7 +88,9 @@ function updateAppJson(cwd, serverUrl) {
|
|
|
59
88
|
if (!data.expo.updates) {
|
|
60
89
|
data.expo.updates = {};
|
|
61
90
|
}
|
|
62
|
-
const updateUrl =
|
|
91
|
+
const updateUrl = projectId
|
|
92
|
+
? `${serverUrl.replace(/\/$/, "")}/api/projects/${projectId}/updates`
|
|
93
|
+
: `${serverUrl.replace(/\/$/, "")}/api/updates`;
|
|
63
94
|
data.expo.updates.url = updateUrl;
|
|
64
95
|
fs.writeFileSync(appJsonPath, JSON.stringify(data, null, 2), "utf-8");
|
|
65
96
|
console.log(`✅ Updated app.json: expo.updates.url set to "${updateUrl}"`);
|
|
@@ -176,16 +207,19 @@ program
|
|
|
176
207
|
.command("init")
|
|
177
208
|
.description("Generate an ECDSA P-256 key pair and write edge-ota.config.json")
|
|
178
209
|
.option("-s, --server <url>", "EdgeOTA server URL")
|
|
210
|
+
.option("--project <id>", "EdgeOTA Project ID (optional)")
|
|
179
211
|
.action(async (options) => {
|
|
212
|
+
printBanner();
|
|
180
213
|
let serverUrl = options.server;
|
|
214
|
+
let projectId = options.project;
|
|
181
215
|
if (!serverUrl) {
|
|
182
|
-
console.log(
|
|
216
|
+
console.log(`${colors.bold}${colors.cyan}ℹ️ Server URL not provided via -s/--server.${colors.reset}`);
|
|
183
217
|
const answer = await askQuestion("Enter your EdgeOTA server URL [http://localhost:3000]: ");
|
|
184
218
|
if (!answer) {
|
|
185
219
|
serverUrl = "http://localhost:3000";
|
|
186
220
|
}
|
|
187
221
|
else if (answer.toLowerCase().includes("host")) {
|
|
188
|
-
console.log(
|
|
222
|
+
console.log(`\n${colors.bold}${colors.yellow}ℹ️ Please set the server URL to your hosted EdgeOTA instance (e.g. https://api.edge-ota.renbo.site)${colors.reset}`);
|
|
189
223
|
const hostedAnswer = await askQuestion("Hosted server URL: ");
|
|
190
224
|
serverUrl = hostedAnswer.trim() || "http://localhost:3000";
|
|
191
225
|
}
|
|
@@ -193,14 +227,24 @@ program
|
|
|
193
227
|
serverUrl = answer;
|
|
194
228
|
}
|
|
195
229
|
}
|
|
230
|
+
if (!projectId && !options.server) {
|
|
231
|
+
console.log(`\n${colors.bold}${colors.cyan}ℹ️ Project ID not provided via --project.${colors.reset}`);
|
|
232
|
+
const pAnswer = await askQuestion("Enter your EdgeOTA Project ID (optional, press Enter for single-tenant self-hosted): ");
|
|
233
|
+
if (pAnswer.trim()) {
|
|
234
|
+
projectId = pAnswer.trim();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
196
237
|
// Normalise URL (remove trailing slashes)
|
|
197
238
|
serverUrl = serverUrl.replace(/\/$/, "");
|
|
198
|
-
console.log(
|
|
239
|
+
console.log(`⚙️ Generating ECDSA P-256 key pair...`);
|
|
199
240
|
const keys = await generateECDSAKeyPair();
|
|
200
241
|
const config = {
|
|
201
242
|
serverUrl: serverUrl,
|
|
202
243
|
publicKey: keys.publicKey
|
|
203
244
|
};
|
|
245
|
+
if (projectId) {
|
|
246
|
+
config.projectId = projectId;
|
|
247
|
+
}
|
|
204
248
|
const cwd = process.cwd();
|
|
205
249
|
fs.writeFileSync("edge-ota.config.json", JSON.stringify(config, null, 2));
|
|
206
250
|
fs.writeFileSync(".edge-ota.private.key", keys.privateKey, { mode: 0o600 });
|
|
@@ -218,19 +262,22 @@ program
|
|
|
218
262
|
fs.writeFileSync(gitignorePath, `${gitignoreEntry}\n`);
|
|
219
263
|
}
|
|
220
264
|
// Auto-update app.json updates.url
|
|
221
|
-
updateAppJson(cwd, serverUrl);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
console.log(
|
|
226
|
-
console.log(
|
|
227
|
-
console.log(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
265
|
+
updateAppJson(cwd, serverUrl, projectId);
|
|
266
|
+
const targetUrlStr = projectId
|
|
267
|
+
? `${serverUrl}/api/projects/${projectId}/updates`
|
|
268
|
+
: `${serverUrl}/api/updates`;
|
|
269
|
+
console.log(`\n${colors.bold}${colors.green}✔ EdgeOTA initialised successfully!${colors.reset}`);
|
|
270
|
+
console.log(`${colors.gray}──────────────────────────────────────────────────${colors.reset}`);
|
|
271
|
+
console.log(`📂 ${colors.bold}Config :${colors.reset} ${colors.cyan}edge-ota.config.json${colors.reset}`);
|
|
272
|
+
console.log(`🔑 ${colors.bold}Key :${colors.reset} ${colors.yellow}.edge-ota.private.key${colors.reset} ${colors.dim}(keep secret!)${colors.reset}`);
|
|
273
|
+
console.log(`\n${colors.bold}${colors.white}📋 Public key to paste into dashboard → Settings → General:${colors.reset}\n`);
|
|
274
|
+
console.log(`${colors.gray}${keys.publicKey}${colors.reset}`);
|
|
275
|
+
console.log(`\n${colors.bold}${colors.white}💡 Auto-configured app.json updates endpoint:${colors.reset}`);
|
|
276
|
+
console.log(` ${colors.dim}"updates": { "url": "${colors.reset}${colors.cyan}${targetUrlStr}${colors.reset}${colors.dim}" }${colors.reset}\n`);
|
|
277
|
+
console.log(`${colors.gray}──────────────────────────────────────────────────${colors.reset}`);
|
|
278
|
+
console.log(`✨ ${colors.bold}${colors.white}Powered by EdgeOTA${colors.reset} — A product of ${colors.magenta}Renbo Studios${colors.reset}`);
|
|
279
|
+
console.log(`🚀 Need scalable app development or custom Cloudflare setup?`);
|
|
280
|
+
console.log(`👉 Partner with us at ${colors.underline}${colors.cyan}https://renbostudios.com${colors.reset} to launch your next project!\n`);
|
|
234
281
|
});
|
|
235
282
|
// ──────────────────────────────────────────────────────
|
|
236
283
|
// edge-ota push
|
|
@@ -241,6 +288,7 @@ program
|
|
|
241
288
|
.option("-c, --channel <channel>", "Deployment channel", "production")
|
|
242
289
|
.option("-r, --runtime <version>", "React Native runtime version (semver)", "1.0.0")
|
|
243
290
|
.option("-p, --platform <platform>", "Target platform: ios | android | all", "all")
|
|
291
|
+
.option("--project <id>", "Project ID (optional, overrides config)")
|
|
244
292
|
.option("--skip-export", "Skip expo export (use existing ./dist directory)")
|
|
245
293
|
.option("--dry-run", "Build and sign the payload but do NOT upload")
|
|
246
294
|
.action(async (options) => {
|
|
@@ -248,35 +296,36 @@ program
|
|
|
248
296
|
const config = loadConfig(cwd);
|
|
249
297
|
const privateKey = loadPrivateKey(cwd);
|
|
250
298
|
// ── Step 1: Build ────────────────────────────────────────────────────────
|
|
299
|
+
printBanner();
|
|
251
300
|
const distDir = path.resolve(cwd, "dist");
|
|
252
301
|
if (!options.skipExport) {
|
|
253
|
-
console.log(
|
|
302
|
+
console.log(`${colors.bold}${colors.cyan}📦 Running expo export...${colors.reset}`);
|
|
254
303
|
try {
|
|
255
304
|
execSync("npx expo export", { stdio: "inherit", cwd });
|
|
256
305
|
}
|
|
257
306
|
catch {
|
|
258
|
-
console.error(
|
|
307
|
+
console.error(`${colors.bold}${colors.red}❌ expo export failed. Aborting.${colors.reset}`);
|
|
259
308
|
process.exit(1);
|
|
260
309
|
}
|
|
261
310
|
}
|
|
262
311
|
else {
|
|
263
|
-
console.log(
|
|
312
|
+
console.log(`${colors.bold}${colors.yellow}⏭️ Skipping expo export (--skip-export)${colors.reset}`);
|
|
264
313
|
}
|
|
265
314
|
if (!fs.existsSync(distDir)) {
|
|
266
|
-
console.error(
|
|
315
|
+
console.error(`${colors.bold}${colors.red}❌ ./dist directory not found after export. Aborting.${colors.reset}`);
|
|
267
316
|
process.exit(1);
|
|
268
317
|
}
|
|
269
318
|
// ── Step 2: Collect assets & hashes ─────────────────────────────────────
|
|
270
|
-
console.log(
|
|
319
|
+
console.log(`\n${colors.bold}${colors.cyan}🔍 Collecting assets and computing SHA-256 hashes...${colors.reset}`);
|
|
271
320
|
const assets = await collectAssets(distDir);
|
|
272
|
-
console.log(` Found ${assets.length} asset(s)`);
|
|
321
|
+
console.log(` Found ${colors.bold}${colors.white}${assets.length}${colors.reset} asset(s)`);
|
|
273
322
|
const platforms = options.platform === "all" ? ["ios", "android"] : [options.platform];
|
|
274
323
|
for (const platform of platforms) {
|
|
275
|
-
console.log(`\n🚀 Processing platform: ${platform.toUpperCase()}`);
|
|
324
|
+
console.log(`\n${colors.bold}${colors.magenta}🚀 Processing platform: ${platform.toUpperCase()}${colors.reset}`);
|
|
276
325
|
// Find the JS bundle
|
|
277
326
|
const bundlePath = findBundle(distDir, platform);
|
|
278
327
|
if (!bundlePath) {
|
|
279
|
-
console.warn(
|
|
328
|
+
console.warn(`${colors.bold}${colors.yellow}⚠️ No bundle found for ${platform}. Skipping.${colors.reset}`);
|
|
280
329
|
continue;
|
|
281
330
|
}
|
|
282
331
|
const bundleHash = await hashFile(bundlePath);
|
|
@@ -291,55 +340,50 @@ program
|
|
|
291
340
|
assetCount: assets.length
|
|
292
341
|
};
|
|
293
342
|
const payloadStr = JSON.stringify(payloadObj);
|
|
294
|
-
console.log(
|
|
343
|
+
console.log(`🔏 Signing payload with ECDSA P-256...`);
|
|
295
344
|
const signature = await signPayload(payloadStr, privateKey);
|
|
296
|
-
console.log(` Signature: ${signature.slice(0, 24)}
|
|
345
|
+
console.log(` Signature: ${colors.dim}${signature.slice(0, 24)}…${colors.reset}`);
|
|
297
346
|
if (options.dryRun) {
|
|
298
|
-
console.log(
|
|
347
|
+
console.log(`${colors.bold}${colors.yellow}🏳️ Dry run — skipping upload.${colors.reset}`);
|
|
299
348
|
console.log(" Payload:", payloadObj);
|
|
300
349
|
continue;
|
|
301
350
|
}
|
|
302
351
|
// ── Step 4: Upload ─────────────────────────────────────────────────────
|
|
303
|
-
// Pack the whole dist directory into a zip for server-side extraction
|
|
304
352
|
const zipPath = path.resolve(cwd, `update-bundle-${platform}.zip`);
|
|
305
|
-
console.log(
|
|
353
|
+
console.log(`🗜️ Zipping bundle...`);
|
|
306
354
|
await zipDirectory(distDir, zipPath);
|
|
307
355
|
const zipBuffer = fs.readFileSync(zipPath);
|
|
308
|
-
// Build multipart/form-data manually (Node 18+ has FormData but
|
|
309
|
-
// Blob.arrayBuffer is not always available; use Buffer directly).
|
|
310
356
|
const form = new FormData();
|
|
311
357
|
form.append("bundle", new Blob([zipBuffer], { type: "application/zip" }), `bundle-${platform}.zip`);
|
|
312
358
|
form.append("payload", payloadStr);
|
|
313
359
|
form.append("signature", signature);
|
|
314
360
|
form.append("platform", platform);
|
|
315
|
-
console.log(`📡 Uploading to ${config.serverUrl}/api/updates ...`);
|
|
361
|
+
console.log(`📡 Uploading to ${colors.cyan}${config.serverUrl}/api/updates${colors.reset} ...`);
|
|
316
362
|
const response = await fetch(`${config.serverUrl}/api/updates`, {
|
|
317
363
|
method: "POST",
|
|
318
364
|
body: form
|
|
319
|
-
// Note: do NOT set Content-Type; fetch sets it automatically
|
|
320
|
-
// with the correct multipart boundary.
|
|
321
365
|
});
|
|
322
366
|
// Clean up zip
|
|
323
367
|
fs.unlinkSync(zipPath);
|
|
324
368
|
if (response.ok) {
|
|
325
369
|
const body = await response.json().catch(() => ({}));
|
|
326
370
|
const updateId = body.updateId || "unknown";
|
|
327
|
-
console.log(`\n
|
|
328
|
-
console.log(` Update ID
|
|
329
|
-
console.log(` Channel
|
|
330
|
-
console.log(` Runtime
|
|
331
|
-
console.log(` Bundle
|
|
371
|
+
console.log(`\n${colors.bold}${colors.green}✔ ${platform.toUpperCase()} update deployed successfully!${colors.reset}`);
|
|
372
|
+
console.log(` ${colors.bold}Update ID :${colors.reset} ${colors.cyan}${updateId}${colors.reset}`);
|
|
373
|
+
console.log(` ${colors.bold}Channel :${colors.reset} ${colors.white}${options.channel}${colors.reset}`);
|
|
374
|
+
console.log(` ${colors.bold}Runtime :${colors.reset} ${colors.white}${options.runtime}${colors.reset}`);
|
|
375
|
+
console.log(` ${colors.bold}Bundle :${colors.reset} ${colors.gray}${bundleHash.slice(0, 16)}…${colors.reset}`);
|
|
332
376
|
}
|
|
333
377
|
else {
|
|
334
378
|
const text = await response.text();
|
|
335
|
-
console.error(`\n❌ Upload failed (HTTP ${response.status}): ${text}`);
|
|
379
|
+
console.error(`\n${colors.bold}${colors.red}❌ Upload failed (HTTP ${response.status}): ${text}${colors.reset}`);
|
|
336
380
|
process.exit(1);
|
|
337
381
|
}
|
|
338
382
|
}
|
|
339
|
-
console.log(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
383
|
+
console.log(`\n${colors.bold}${colors.green}🎉 Done. Your app will receive this update on the next OTA sync.${colors.reset}\n` +
|
|
384
|
+
`\n✨ ${colors.bold}${colors.white}Powered by EdgeOTA${colors.reset} — A product of ${colors.magenta}Renbo Studios${colors.reset}` +
|
|
385
|
+
`\n🚀 Need scalable app development or custom Cloudflare setup?` +
|
|
386
|
+
`\n👉 Partner with us at ${colors.underline}${colors.cyan}https://renbostudios.com${colors.reset} to launch your next project!\n`);
|
|
343
387
|
});
|
|
344
388
|
// ──────────────────────────────────────────────────────
|
|
345
389
|
// edge-ota status
|
|
@@ -349,16 +393,22 @@ program
|
|
|
349
393
|
.description("List recent releases from the EdgeOTA server")
|
|
350
394
|
.option("-t, --token <token>", "Auth token (or set EDGE_OTA_TOKEN env var)")
|
|
351
395
|
.option("-n, --limit <n>", "Number of releases to show", "10")
|
|
396
|
+
.option("--project <id>", "Project ID (optional)")
|
|
352
397
|
.action(async (options) => {
|
|
353
398
|
const cwd = process.cwd();
|
|
354
399
|
const config = loadConfig(cwd);
|
|
355
400
|
const token = options.token || process.env.EDGE_OTA_TOKEN;
|
|
401
|
+
const projectId = options.project || config.projectId;
|
|
356
402
|
if (!token) {
|
|
357
403
|
console.error("❌ Auth token required. Use -t <token> or set EDGE_OTA_TOKEN.");
|
|
358
404
|
process.exit(1);
|
|
359
405
|
}
|
|
406
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
407
|
+
if (projectId) {
|
|
408
|
+
headers["x-project-id"] = projectId;
|
|
409
|
+
}
|
|
360
410
|
const res = await fetch(`${config.serverUrl}/api/releases`, {
|
|
361
|
-
headers
|
|
411
|
+
headers
|
|
362
412
|
});
|
|
363
413
|
if (!res.ok) {
|
|
364
414
|
console.error(`❌ Failed to fetch releases (HTTP ${res.status})`);
|
package/package.json
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@renbostudios/edge-ota",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
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/
|
|
8
|
+
"homepage": "https://github.com/renbostudios/edge-ota",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/
|
|
11
|
+
"url": "https://github.com/renbostudios/edge-ota.git",
|
|
12
|
+
"directory": "apps/cli"
|
|
12
13
|
},
|
|
13
14
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
15
|
+
"url": "https://github.com/renbostudios/edge-ota/issues"
|
|
15
16
|
},
|
|
16
17
|
"keywords": ["expo", "ota", "updates", "react-native", "cli", "edge", "cloudflare"],
|
|
17
18
|
"type": "module",
|