@renbostudios/edge-ota 0.2.1 → 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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/dist/index.js +70 -44
  3. package/package.json +5 -4
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
@@ -266,35 +296,36 @@ program
266
296
  const config = loadConfig(cwd);
267
297
  const privateKey = loadPrivateKey(cwd);
268
298
  // ── Step 1: Build ────────────────────────────────────────────────────────
299
+ printBanner();
269
300
  const distDir = path.resolve(cwd, "dist");
270
301
  if (!options.skipExport) {
271
- console.log("📦 Running expo export...");
302
+ console.log(`${colors.bold}${colors.cyan}📦 Running expo export...${colors.reset}`);
272
303
  try {
273
304
  execSync("npx expo export", { stdio: "inherit", cwd });
274
305
  }
275
306
  catch {
276
- console.error("❌ expo export failed. Aborting.");
307
+ console.error(`${colors.bold}${colors.red}❌ expo export failed. Aborting.${colors.reset}`);
277
308
  process.exit(1);
278
309
  }
279
310
  }
280
311
  else {
281
- console.log("⏭️ Skipping expo export (--skip-export)");
312
+ console.log(`${colors.bold}${colors.yellow}⏭️ Skipping expo export (--skip-export)${colors.reset}`);
282
313
  }
283
314
  if (!fs.existsSync(distDir)) {
284
- console.error("❌ ./dist directory not found after export. Aborting.");
315
+ console.error(`${colors.bold}${colors.red}❌ ./dist directory not found after export. Aborting.${colors.reset}`);
285
316
  process.exit(1);
286
317
  }
287
318
  // ── Step 2: Collect assets & hashes ─────────────────────────────────────
288
- console.log("🔍 Collecting assets and computing SHA-256 hashes...");
319
+ console.log(`\n${colors.bold}${colors.cyan}🔍 Collecting assets and computing SHA-256 hashes...${colors.reset}`);
289
320
  const assets = await collectAssets(distDir);
290
- console.log(` Found ${assets.length} asset(s)`);
321
+ console.log(` Found ${colors.bold}${colors.white}${assets.length}${colors.reset} asset(s)`);
291
322
  const platforms = options.platform === "all" ? ["ios", "android"] : [options.platform];
292
323
  for (const platform of platforms) {
293
- console.log(`\n🚀 Processing platform: ${platform.toUpperCase()}`);
324
+ console.log(`\n${colors.bold}${colors.magenta}🚀 Processing platform: ${platform.toUpperCase()}${colors.reset}`);
294
325
  // Find the JS bundle
295
326
  const bundlePath = findBundle(distDir, platform);
296
327
  if (!bundlePath) {
297
- console.warn(`⚠️ No bundle found for ${platform}. Skipping.`);
328
+ console.warn(`${colors.bold}${colors.yellow}⚠️ No bundle found for ${platform}. Skipping.${colors.reset}`);
298
329
  continue;
299
330
  }
300
331
  const bundleHash = await hashFile(bundlePath);
@@ -309,55 +340,50 @@ program
309
340
  assetCount: assets.length
310
341
  };
311
342
  const payloadStr = JSON.stringify(payloadObj);
312
- console.log("🔏 Signing payload with ECDSA P-256...");
343
+ console.log(`🔏 Signing payload with ECDSA P-256...`);
313
344
  const signature = await signPayload(payloadStr, privateKey);
314
- console.log(` Signature: ${signature.slice(0, 24)}…`);
345
+ console.log(` Signature: ${colors.dim}${signature.slice(0, 24)}…${colors.reset}`);
315
346
  if (options.dryRun) {
316
- console.log("🏳️ Dry run — skipping upload.");
347
+ console.log(`${colors.bold}${colors.yellow}🏳️ Dry run — skipping upload.${colors.reset}`);
317
348
  console.log(" Payload:", payloadObj);
318
349
  continue;
319
350
  }
320
351
  // ── Step 4: Upload ─────────────────────────────────────────────────────
321
- // Pack the whole dist directory into a zip for server-side extraction
322
352
  const zipPath = path.resolve(cwd, `update-bundle-${platform}.zip`);
323
- console.log("🗜️ Zipping bundle...");
353
+ console.log(`🗜️ Zipping bundle...`);
324
354
  await zipDirectory(distDir, zipPath);
325
355
  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
356
  const form = new FormData();
329
357
  form.append("bundle", new Blob([zipBuffer], { type: "application/zip" }), `bundle-${platform}.zip`);
330
358
  form.append("payload", payloadStr);
331
359
  form.append("signature", signature);
332
360
  form.append("platform", platform);
333
- console.log(`📡 Uploading to ${config.serverUrl}/api/updates ...`);
361
+ console.log(`📡 Uploading to ${colors.cyan}${config.serverUrl}/api/updates${colors.reset} ...`);
334
362
  const response = await fetch(`${config.serverUrl}/api/updates`, {
335
363
  method: "POST",
336
364
  body: form
337
- // Note: do NOT set Content-Type; fetch sets it automatically
338
- // with the correct multipart boundary.
339
365
  });
340
366
  // Clean up zip
341
367
  fs.unlinkSync(zipPath);
342
368
  if (response.ok) {
343
369
  const body = await response.json().catch(() => ({}));
344
370
  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)}…`);
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}`);
350
376
  }
351
377
  else {
352
378
  const text = await response.text();
353
- 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}`);
354
380
  process.exit(1);
355
381
  }
356
382
  }
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!");
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`);
361
387
  });
362
388
  // ──────────────────────────────────────────────────────
363
389
  // edge-ota status
package/package.json CHANGED
@@ -1,17 +1,18 @@
1
1
  {
2
2
  "name": "@renbostudios/edge-ota",
3
- "version": "0.2.1",
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/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
+ "directory": "apps/cli"
12
13
  },
13
14
  "bugs": {
14
- "url": "https://github.com/Renbo-Studios/edge-ota/issues"
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",