@renbostudios/edge-ota 0.2.1 → 0.2.3

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 (3) hide show
  1. package/README.md +3 -3
  2. package/dist/index.js +92 -45
  3. package/package.json +13 -5
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # `@renbostudios/edge-ota`
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 @renbostudios/edge-ota
9
+ npm install -g @edge-ota/cli
10
10
  # or use without installing:
11
- npx @renbostudios/edge-ota <command>
11
+ npx @edge-ota/cli <command>
12
12
  ```
13
13
 
14
14
  ## Quick Start
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,
@@ -180,16 +209,17 @@ program
180
209
  .option("-s, --server <url>", "EdgeOTA server URL")
181
210
  .option("--project <id>", "EdgeOTA Project ID (optional)")
182
211
  .action(async (options) => {
212
+ printBanner();
183
213
  let serverUrl = options.server;
184
214
  let projectId = options.project;
185
215
  if (!serverUrl) {
186
- console.log("ℹ️ Server URL not provided via -s/--server.");
216
+ console.log(`${colors.bold}${colors.cyan}ℹ️ Server URL not provided via -s/--server.${colors.reset}`);
187
217
  const answer = await askQuestion("Enter your EdgeOTA server URL [http://localhost:3000]: ");
188
218
  if (!answer) {
189
219
  serverUrl = "http://localhost:3000";
190
220
  }
191
221
  else if (answer.toLowerCase().includes("host")) {
192
- console.log("\nℹ️ Please set the server URL to your hosted EdgeOTA instance (e.g. https://ota.renbostudios.com)");
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}`);
193
223
  const hostedAnswer = await askQuestion("Hosted server URL: ");
194
224
  serverUrl = hostedAnswer.trim() || "http://localhost:3000";
195
225
  }
@@ -198,7 +228,7 @@ program
198
228
  }
199
229
  }
200
230
  if (!projectId && !options.server) {
201
- console.log("\nℹ️ Project ID not provided via --project.");
231
+ console.log(`\n${colors.bold}${colors.cyan}ℹ️ Project ID not provided via --project.${colors.reset}`);
202
232
  const pAnswer = await askQuestion("Enter your EdgeOTA Project ID (optional, press Enter for single-tenant self-hosted): ");
203
233
  if (pAnswer.trim()) {
204
234
  projectId = pAnswer.trim();
@@ -206,7 +236,7 @@ program
206
236
  }
207
237
  // Normalise URL (remove trailing slashes)
208
238
  serverUrl = serverUrl.replace(/\/$/, "");
209
- console.log("⚙️ Generating ECDSA P-256 key pair...");
239
+ console.log(`⚙️ Generating ECDSA P-256 key pair...`);
210
240
  const keys = await generateECDSAKeyPair();
211
241
  const config = {
212
242
  serverUrl: serverUrl,
@@ -236,18 +266,18 @@ program
236
266
  const targetUrlStr = projectId
237
267
  ? `${serverUrl}/api/projects/${projectId}/updates`
238
268
  : `${serverUrl}/api/updates`;
239
- console.log("\n✅ EdgeOTA initialised.");
240
- console.log(" Config : edge-ota.config.json");
241
- console.log(" Key : .edge-ota.private.key (keep this secret!)");
242
- console.log("\n📋 Public key to paste into your dashboard → Settings → Infrastructure:\n");
243
- console.log(keys.publicKey);
244
- console.log("\n💡 Your app.json updates configuration has been configured to:\n" +
245
- ' "updates": {\n' +
246
- ' "url": "' + targetUrlStr + '"\n' +
247
- " }\n" +
248
- "\n✨ Powered by EdgeOTA — A product of Renbo Studios" +
249
- "\n🚀 Need scalable app development or custom Cloudflare setup?" +
250
- "\n👉 Partner with us at https://renbostudios.com to launch your next project!");
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`);
251
281
  });
252
282
  // ──────────────────────────────────────────────────────
253
283
  // edge-ota push
@@ -259,42 +289,60 @@ program
259
289
  .option("-r, --runtime <version>", "React Native runtime version (semver)", "1.0.0")
260
290
  .option("-p, --platform <platform>", "Target platform: ios | android | all", "all")
261
291
  .option("--project <id>", "Project ID (optional, overrides config)")
292
+ .option("-t, --token <token>", "Deploy key token (or set EDGE_OTA_TOKEN env var)")
262
293
  .option("--skip-export", "Skip expo export (use existing ./dist directory)")
263
294
  .option("--dry-run", "Build and sign the payload but do NOT upload")
264
295
  .action(async (options) => {
265
296
  const cwd = process.cwd();
266
297
  const config = loadConfig(cwd);
267
298
  const privateKey = loadPrivateKey(cwd);
299
+ // Resolve token — CLI flag > env var
300
+ const token = options.token || process.env.EDGE_OTA_TOKEN;
301
+ if (!token) {
302
+ console.error(`${colors.bold}${colors.red}❌ Auth token required. Use --token <token> or set EDGE_OTA_TOKEN env var.${colors.reset}`);
303
+ console.error(` Generate a deploy key from your dashboard → Keys page.`);
304
+ process.exit(1);
305
+ }
306
+ // Resolve project ID — CLI flag > config file
307
+ const projectId = options.project || config.projectId;
308
+ // Build the correct upload URL
309
+ const uploadUrl = projectId
310
+ ? `${config.serverUrl}/api/projects/${projectId}/updates`
311
+ : `${config.serverUrl}/api/updates`;
268
312
  // ── Step 1: Build ────────────────────────────────────────────────────────
313
+ printBanner();
269
314
  const distDir = path.resolve(cwd, "dist");
270
315
  if (!options.skipExport) {
271
- console.log("📦 Running expo export...");
316
+ console.log(`${colors.bold}${colors.cyan}📦 Running expo export...${colors.reset}`);
272
317
  try {
273
318
  execSync("npx expo export", { stdio: "inherit", cwd });
274
319
  }
275
320
  catch {
276
- console.error("❌ expo export failed. Aborting.");
321
+ console.error(`${colors.bold}${colors.red}❌ expo export failed. Aborting.${colors.reset}`);
277
322
  process.exit(1);
278
323
  }
279
324
  }
280
325
  else {
281
- console.log("⏭️ Skipping expo export (--skip-export)");
326
+ console.log(`${colors.bold}${colors.yellow}⏭️ Skipping expo export (--skip-export)${colors.reset}`);
282
327
  }
283
328
  if (!fs.existsSync(distDir)) {
284
- console.error("❌ ./dist directory not found after export. Aborting.");
329
+ console.error(`${colors.bold}${colors.red}❌ ./dist directory not found after export. Aborting.${colors.reset}`);
285
330
  process.exit(1);
286
331
  }
287
332
  // ── Step 2: Collect assets & hashes ─────────────────────────────────────
288
- console.log("🔍 Collecting assets and computing SHA-256 hashes...");
333
+ console.log(`\n${colors.bold}${colors.cyan}🔍 Collecting assets and computing SHA-256 hashes...${colors.reset}`);
289
334
  const assets = await collectAssets(distDir);
290
- console.log(` Found ${assets.length} asset(s)`);
335
+ console.log(` Found ${colors.bold}${colors.white}${assets.length}${colors.reset} asset(s)`);
336
+ if (projectId) {
337
+ console.log(` Project : ${colors.bold}${colors.cyan}${projectId}${colors.reset}`);
338
+ }
291
339
  const platforms = options.platform === "all" ? ["ios", "android"] : [options.platform];
292
340
  for (const platform of platforms) {
293
- console.log(`\n🚀 Processing platform: ${platform.toUpperCase()}`);
341
+ console.log(`\n${colors.bold}${colors.magenta}🚀 Processing platform: ${platform.toUpperCase()}${colors.reset}`);
294
342
  // Find the JS bundle
295
343
  const bundlePath = findBundle(distDir, platform);
296
344
  if (!bundlePath) {
297
- console.warn(`⚠️ No bundle found for ${platform}. Skipping.`);
345
+ console.warn(`${colors.bold}${colors.yellow}⚠️ No bundle found for ${platform}. Skipping.${colors.reset}`);
298
346
  continue;
299
347
  }
300
348
  const bundleHash = await hashFile(bundlePath);
@@ -309,55 +357,54 @@ program
309
357
  assetCount: assets.length
310
358
  };
311
359
  const payloadStr = JSON.stringify(payloadObj);
312
- console.log("🔏 Signing payload with ECDSA P-256...");
360
+ console.log(`🔏 Signing payload with ECDSA P-256...`);
313
361
  const signature = await signPayload(payloadStr, privateKey);
314
- console.log(` Signature: ${signature.slice(0, 24)}…`);
362
+ console.log(` Signature: ${colors.dim}${signature.slice(0, 24)}…${colors.reset}`);
315
363
  if (options.dryRun) {
316
- console.log("🏳️ Dry run — skipping upload.");
364
+ console.log(`${colors.bold}${colors.yellow}🏳️ Dry run — skipping upload.${colors.reset}`);
317
365
  console.log(" Payload:", payloadObj);
318
366
  continue;
319
367
  }
320
368
  // ── Step 4: Upload ─────────────────────────────────────────────────────
321
- // Pack the whole dist directory into a zip for server-side extraction
322
369
  const zipPath = path.resolve(cwd, `update-bundle-${platform}.zip`);
323
- console.log("🗜️ Zipping bundle...");
370
+ console.log(`🗜️ Zipping bundle...`);
324
371
  await zipDirectory(distDir, zipPath);
325
372
  const zipBuffer = fs.readFileSync(zipPath);
326
- // Build multipart/form-data manually (Node 18+ has FormData but
327
- // Blob.arrayBuffer is not always available; use Buffer directly).
328
373
  const form = new FormData();
329
374
  form.append("bundle", new Blob([zipBuffer], { type: "application/zip" }), `bundle-${platform}.zip`);
330
375
  form.append("payload", payloadStr);
331
376
  form.append("signature", signature);
332
377
  form.append("platform", platform);
333
- console.log(`📡 Uploading to ${config.serverUrl}/api/updates ...`);
334
- const response = await fetch(`${config.serverUrl}/api/updates`, {
378
+ console.log(`📡 Uploading to ${colors.cyan}${uploadUrl}${colors.reset} ...`);
379
+ const response = await fetch(uploadUrl, {
335
380
  method: "POST",
381
+ headers: {
382
+ "Authorization": `Bearer ${token}`,
383
+ ...(projectId ? { "X-Project-Id": projectId } : {})
384
+ },
336
385
  body: form
337
- // Note: do NOT set Content-Type; fetch sets it automatically
338
- // with the correct multipart boundary.
339
386
  });
340
387
  // Clean up zip
341
388
  fs.unlinkSync(zipPath);
342
389
  if (response.ok) {
343
390
  const body = await response.json().catch(() => ({}));
344
391
  const updateId = body.updateId || "unknown";
345
- console.log(`\n✅ ${platform.toUpperCase()} update deployed!`);
346
- console.log(` Update ID : ${updateId}`);
347
- console.log(` Channel : ${options.channel}`);
348
- console.log(` Runtime : ${options.runtime}`);
349
- console.log(` Bundle : ${bundleHash.slice(0, 16)}…`);
392
+ console.log(`\n${colors.bold}${colors.green}✔ ${platform.toUpperCase()} update deployed successfully!${colors.reset}`);
393
+ console.log(` ${colors.bold}Update ID :${colors.reset} ${colors.cyan}${updateId}${colors.reset}`);
394
+ console.log(` ${colors.bold}Channel :${colors.reset} ${colors.white}${options.channel}${colors.reset}`);
395
+ console.log(` ${colors.bold}Runtime :${colors.reset} ${colors.white}${options.runtime}${colors.reset}`);
396
+ console.log(` ${colors.bold}Bundle :${colors.reset} ${colors.gray}${bundleHash.slice(0, 16)}…${colors.reset}`);
350
397
  }
351
398
  else {
352
399
  const text = await response.text();
353
- console.error(`\n❌ Upload failed (HTTP ${response.status}): ${text}`);
400
+ console.error(`\n${colors.bold}${colors.red}❌ Upload failed (HTTP ${response.status}): ${text}${colors.reset}`);
354
401
  process.exit(1);
355
402
  }
356
403
  }
357
- console.log("\n🎉 Done. Your app will receive this update on the next OTA sync.\n" +
358
- "\n✨ Powered by EdgeOTA — A product of Renbo Studios" +
359
- "\n🚀 Need scalable app development or custom Cloudflare setup?" +
360
- "\n👉 Partner with us at https://renbostudios.com to launch your next project!");
404
+ console.log(`\n${colors.bold}${colors.green}🎉 Done. Your app will receive this update on the next OTA sync.${colors.reset}\n` +
405
+ `\n✨ ${colors.bold}${colors.white}Powered by EdgeOTA${colors.reset} — A product of ${colors.magenta}Renbo Studios${colors.reset}` +
406
+ `\n🚀 Need scalable app development or custom Cloudflare setup?` +
407
+ `\n👉 Partner with us at ${colors.underline}${colors.cyan}https://renbostudios.com${colors.reset} to launch your next project!\n`);
361
408
  });
362
409
  // ──────────────────────────────────────────────────────
363
410
  // edge-ota status
package/package.json CHANGED
@@ -1,19 +1,27 @@
1
1
  {
2
2
  "name": "@renbostudios/edge-ota",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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/Renbo-Studios/edge-ota",
8
+ "homepage": "https://github.com/renbostudios/edge-ota",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/Renbo-Studios/edge-ota.git"
11
+ "url": "https://github.com/renbostudios/edge-ota.git"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://github.com/Renbo-Studios/edge-ota/issues"
14
+ "url": "https://github.com/renbostudios/edge-ota/issues"
15
15
  },
16
- "keywords": ["expo", "ota", "updates", "react-native", "cli", "edge", "cloudflare"],
16
+ "keywords": [
17
+ "expo",
18
+ "ota",
19
+ "updates",
20
+ "react-native",
21
+ "cli",
22
+ "edge",
23
+ "cloudflare"
24
+ ],
17
25
  "type": "module",
18
26
  "bin": {
19
27
  "edge-ota": "./dist/index.js"