@renbostudios/edge-ota 0.2.2 → 0.2.4

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 (2) hide show
  1. package/dist/index.js +106 -93
  2. package/package.json +11 -4
package/dist/index.js CHANGED
@@ -32,6 +32,27 @@ 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
+ // ─── Auto-load .env from the project root ────────────────────────────────────
36
+ // Lets users store EDGE_OTA_TOKEN in a .env file without shell exports.
37
+ try {
38
+ const envPath = path.resolve(process.cwd(), ".env");
39
+ if (fs.existsSync(envPath)) {
40
+ const lines = fs.readFileSync(envPath, "utf8").split("\n");
41
+ for (const line of lines) {
42
+ const trimmed = line.trim();
43
+ if (!trimmed || trimmed.startsWith("#"))
44
+ continue;
45
+ const eqIdx = trimmed.indexOf("=");
46
+ if (eqIdx === -1)
47
+ continue;
48
+ const key = trimmed.slice(0, eqIdx).trim();
49
+ const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
50
+ if (!(key in process.env))
51
+ process.env[key] = val;
52
+ }
53
+ }
54
+ }
55
+ catch { /* silently skip if .env is unreadable */ }
35
56
  // ─── ANSI Styling Helpers ───────────────────────────────────────────────────
36
57
  const colors = {
37
58
  reset: "\x1b[0m",
@@ -50,16 +71,11 @@ const colors = {
50
71
  gray: "\x1b[90m",
51
72
  };
52
73
  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`);
74
+ const line = `${colors.dim}${"-".repeat(48)}${colors.reset}`;
75
+ console.log(`\n${line}`);
76
+ console.log(` ${colors.bold}${colors.white}edge-ota${colors.reset} ${colors.dim}v0.2.4 · Zero-SDK OTA for Expo${colors.reset}`);
77
+ console.log(` ${colors.dim}by Renbo Studios · renbostudios.com${colors.reset}`);
78
+ console.log(`${line}\n`);
63
79
  }
64
80
  function askQuestion(query) {
65
81
  const rl = readline.createInterface({
@@ -82,21 +98,19 @@ function updateAppJson(cwd, serverUrl, projectId) {
82
98
  try {
83
99
  const raw = fs.readFileSync(appJsonPath, "utf-8");
84
100
  const data = JSON.parse(raw);
85
- if (!data.expo) {
101
+ if (!data.expo)
86
102
  data.expo = {};
87
- }
88
- if (!data.expo.updates) {
103
+ if (!data.expo.updates)
89
104
  data.expo.updates = {};
90
- }
91
105
  const updateUrl = projectId
92
106
  ? `${serverUrl.replace(/\/$/, "")}/api/projects/${projectId}/updates`
93
107
  : `${serverUrl.replace(/\/$/, "")}/api/updates`;
94
108
  data.expo.updates.url = updateUrl;
95
109
  fs.writeFileSync(appJsonPath, JSON.stringify(data, null, 2), "utf-8");
96
- console.log(`✅ Updated app.json: expo.updates.url set to "${updateUrl}"`);
110
+ console.log(` ${colors.dim}app.json${colors.reset} updates.url set`);
97
111
  }
98
112
  catch (error) {
99
- console.error(`❌ Failed to update app.json: ${error.message}`);
113
+ console.error(` ${colors.red}error${colors.reset} Failed to update app.json: ${error.message}`);
100
114
  }
101
115
  }
102
116
  function loadConfig(cwd) {
@@ -213,14 +227,14 @@ program
213
227
  let serverUrl = options.server;
214
228
  let projectId = options.project;
215
229
  if (!serverUrl) {
216
- console.log(`${colors.bold}${colors.cyan}ℹ️ Server URL not provided via -s/--server.${colors.reset}`);
217
- const answer = await askQuestion("Enter your EdgeOTA server URL [http://localhost:3000]: ");
230
+ console.log(` ${colors.dim}no --server flag provided${colors.reset}`);
231
+ const answer = await askQuestion(` server url [http://localhost:3000]: `);
218
232
  if (!answer) {
219
233
  serverUrl = "http://localhost:3000";
220
234
  }
221
235
  else if (answer.toLowerCase().includes("host")) {
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}`);
223
- const hostedAnswer = await askQuestion("Hosted server URL: ");
236
+ console.log(` ${colors.yellow}hint${colors.reset} use your hosted EdgeOTA instance (e.g. https://api.edge-ota.renbo.site)`);
237
+ const hostedAnswer = await askQuestion(` hosted server url: `);
224
238
  serverUrl = hostedAnswer.trim() || "http://localhost:3000";
225
239
  }
226
240
  else {
@@ -228,56 +242,43 @@ program
228
242
  }
229
243
  }
230
244
  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()) {
245
+ const pAnswer = await askQuestion(` project id (optional, press Enter to skip): `);
246
+ if (pAnswer.trim())
234
247
  projectId = pAnswer.trim();
235
- }
236
248
  }
237
- // Normalise URL (remove trailing slashes)
238
249
  serverUrl = serverUrl.replace(/\/$/, "");
239
- console.log(`⚙️ Generating ECDSA P-256 key pair...`);
250
+ console.log(` ${colors.dim}generating ECDSA P-256 key pair...${colors.reset}`);
240
251
  const keys = await generateECDSAKeyPair();
241
- const config = {
242
- serverUrl: serverUrl,
243
- publicKey: keys.publicKey
244
- };
245
- if (projectId) {
252
+ const config = { serverUrl, publicKey: keys.publicKey };
253
+ if (projectId)
246
254
  config.projectId = projectId;
247
- }
248
255
  const cwd = process.cwd();
249
256
  fs.writeFileSync("edge-ota.config.json", JSON.stringify(config, null, 2));
250
257
  fs.writeFileSync(".edge-ota.private.key", keys.privateKey, { mode: 0o600 });
251
- // Add the private key to .gitignore if it isn't already
252
258
  const gitignorePath = ".gitignore";
253
259
  const gitignoreEntry = ".edge-ota.private.key";
254
260
  if (fs.existsSync(gitignorePath)) {
255
261
  const current = fs.readFileSync(gitignorePath, "utf-8");
256
262
  if (!current.includes(gitignoreEntry)) {
257
263
  fs.appendFileSync(gitignorePath, `\n${gitignoreEntry}\n`);
258
- console.log("✅ Added .edge-ota.private.key to .gitignore");
259
264
  }
260
265
  }
261
266
  else {
262
267
  fs.writeFileSync(gitignorePath, `${gitignoreEntry}\n`);
263
268
  }
264
- // Auto-update app.json updates.url
265
269
  updateAppJson(cwd, serverUrl, projectId);
266
270
  const targetUrlStr = projectId
267
271
  ? `${serverUrl}/api/projects/${projectId}/updates`
268
272
  : `${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`);
273
+ const sep = `${colors.dim}${"-".repeat(48)}${colors.reset}`;
274
+ console.log(`\n ${colors.green}✓${colors.reset} ${colors.bold}initialised${colors.reset}`);
275
+ console.log(sep);
276
+ console.log(` config ${colors.cyan}edge-ota.config.json${colors.reset}`);
277
+ console.log(` key ${colors.yellow}.edge-ota.private.key${colors.reset} ${colors.dim}(keep secret)${colors.reset}`);
278
+ console.log(` endpoint ${colors.dim}${targetUrlStr}${colors.reset}`);
279
+ console.log(sep);
280
+ console.log(`\n ${colors.bold}public key${colors.reset} ${colors.dim}paste into dashboard → Settings → General${colors.reset}\n`);
281
+ console.log(`${colors.dim}${keys.publicKey}${colors.reset}\n`);
281
282
  });
282
283
  // ──────────────────────────────────────────────────────
283
284
  // edge-ota push
@@ -289,48 +290,61 @@ program
289
290
  .option("-r, --runtime <version>", "React Native runtime version (semver)", "1.0.0")
290
291
  .option("-p, --platform <platform>", "Target platform: ios | android | all", "all")
291
292
  .option("--project <id>", "Project ID (optional, overrides config)")
293
+ .option("-t, --token <token>", "Deploy key token (or set EDGE_OTA_TOKEN env var)")
292
294
  .option("--skip-export", "Skip expo export (use existing ./dist directory)")
293
295
  .option("--dry-run", "Build and sign the payload but do NOT upload")
294
296
  .action(async (options) => {
295
297
  const cwd = process.cwd();
296
298
  const config = loadConfig(cwd);
297
299
  const privateKey = loadPrivateKey(cwd);
300
+ // Resolve token — CLI flag > env var
301
+ const token = options.token || process.env.EDGE_OTA_TOKEN;
302
+ if (!token) {
303
+ console.error(`${colors.bold}${colors.red}❌ Auth token required. Use --token <token> or set EDGE_OTA_TOKEN env var.${colors.reset}`);
304
+ console.error(` Generate a deploy key from your dashboard → Keys page.`);
305
+ process.exit(1);
306
+ }
307
+ // Resolve project ID — CLI flag > config file
308
+ const projectId = options.project || config.projectId;
309
+ // Build the correct upload URL
310
+ const uploadUrl = projectId
311
+ ? `${config.serverUrl}/api/projects/${projectId}/updates`
312
+ : `${config.serverUrl}/api/updates`;
298
313
  // ── Step 1: Build ────────────────────────────────────────────────────────
299
314
  printBanner();
300
315
  const distDir = path.resolve(cwd, "dist");
301
316
  if (!options.skipExport) {
302
- console.log(`${colors.bold}${colors.cyan}📦 Running expo export...${colors.reset}`);
317
+ console.log(` ${colors.dim}running expo export...${colors.reset}`);
303
318
  try {
304
319
  execSync("npx expo export", { stdio: "inherit", cwd });
305
320
  }
306
321
  catch {
307
- console.error(`${colors.bold}${colors.red}❌ expo export failed. Aborting.${colors.reset}`);
322
+ console.error(` ${colors.red}error${colors.reset} expo export failed`);
308
323
  process.exit(1);
309
324
  }
310
325
  }
311
326
  else {
312
- console.log(`${colors.bold}${colors.yellow}⏭️ Skipping expo export (--skip-export)${colors.reset}`);
327
+ console.log(` ${colors.dim}skipping expo export (--skip-export)${colors.reset}`);
313
328
  }
314
329
  if (!fs.existsSync(distDir)) {
315
- console.error(`${colors.bold}${colors.red}❌ ./dist directory not found after export. Aborting.${colors.reset}`);
330
+ console.error(` ${colors.red}error${colors.reset} ./dist not found after export`);
316
331
  process.exit(1);
317
332
  }
318
- // ── Step 2: Collect assets & hashes ─────────────────────────────────────
319
- console.log(`\n${colors.bold}${colors.cyan}🔍 Collecting assets and computing SHA-256 hashes...${colors.reset}`);
333
+ console.log(` ${colors.dim}collecting assets...${colors.reset}`);
320
334
  const assets = await collectAssets(distDir);
321
- console.log(` Found ${colors.bold}${colors.white}${assets.length}${colors.reset} asset(s)`);
335
+ console.log(` ${colors.dim}found ${assets.length} asset(s)${projectId ? ` · project ${projectId}` : ""}${colors.reset}`);
322
336
  const platforms = options.platform === "all" ? ["ios", "android"] : [options.platform];
337
+ const sep = `${colors.dim}${"-".repeat(48)}${colors.reset}`;
323
338
  for (const platform of platforms) {
324
- console.log(`\n${colors.bold}${colors.magenta}🚀 Processing platform: ${platform.toUpperCase()}${colors.reset}`);
325
- // Find the JS bundle
339
+ console.log(`\n${sep}`);
340
+ console.log(` ${colors.bold}${platform}${colors.reset}`);
341
+ console.log(sep);
326
342
  const bundlePath = findBundle(distDir, platform);
327
343
  if (!bundlePath) {
328
- console.warn(`${colors.bold}${colors.yellow}⚠️ No bundle found for ${platform}. Skipping.${colors.reset}`);
344
+ console.warn(` ${colors.yellow}warn${colors.reset} no bundle found for ${platform}, skipping`);
329
345
  continue;
330
346
  }
331
347
  const bundleHash = await hashFile(bundlePath);
332
- const bundleAsset = assets.find(a => a.localPath === bundlePath);
333
- // ── Step 3: Build the signed payload ──────────────────────────────────
334
348
  const payloadObj = {
335
349
  channel: options.channel,
336
350
  runtimeVersion: options.runtime,
@@ -340,17 +354,16 @@ program
340
354
  assetCount: assets.length
341
355
  };
342
356
  const payloadStr = JSON.stringify(payloadObj);
343
- console.log(`🔏 Signing payload with ECDSA P-256...`);
357
+ console.log(` signing ECDSA P-256`);
344
358
  const signature = await signPayload(payloadStr, privateKey);
345
- console.log(` Signature: ${colors.dim}${signature.slice(0, 24)}…${colors.reset}`);
359
+ console.log(` sig ${colors.dim}${signature.slice(0, 24)}…${colors.reset}`);
346
360
  if (options.dryRun) {
347
- console.log(`${colors.bold}${colors.yellow}🏳️ Dry run — skipping upload.${colors.reset}`);
348
- console.log(" Payload:", payloadObj);
361
+ console.log(` ${colors.yellow}dry-run${colors.reset} upload skipped`);
362
+ console.log(" payload ", payloadObj);
349
363
  continue;
350
364
  }
351
- // ── Step 4: Upload ─────────────────────────────────────────────────────
352
365
  const zipPath = path.resolve(cwd, `update-bundle-${platform}.zip`);
353
- console.log(`🗜️ Zipping bundle...`);
366
+ console.log(` packing bundle → zip`);
354
367
  await zipDirectory(distDir, zipPath);
355
368
  const zipBuffer = fs.readFileSync(zipPath);
356
369
  const form = new FormData();
@@ -358,32 +371,34 @@ program
358
371
  form.append("payload", payloadStr);
359
372
  form.append("signature", signature);
360
373
  form.append("platform", platform);
361
- console.log(`📡 Uploading to ${colors.cyan}${config.serverUrl}/api/updates${colors.reset} ...`);
362
- const response = await fetch(`${config.serverUrl}/api/updates`, {
374
+ console.log(` uploading ${colors.dim}${uploadUrl}${colors.reset}`);
375
+ const response = await fetch(uploadUrl, {
363
376
  method: "POST",
377
+ headers: {
378
+ "Authorization": `Bearer ${token}`,
379
+ ...(projectId ? { "X-Project-Id": projectId } : {})
380
+ },
364
381
  body: form
365
382
  });
366
- // Clean up zip
367
383
  fs.unlinkSync(zipPath);
368
384
  if (response.ok) {
369
385
  const body = await response.json().catch(() => ({}));
370
386
  const updateId = body.updateId || "unknown";
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}`);
387
+ console.log(` ${colors.green}✓${colors.reset} deployed`);
388
+ console.log(` id ${colors.dim}${updateId}${colors.reset}`);
389
+ console.log(` channel ${options.channel}`);
390
+ console.log(` runtime ${options.runtime}`);
391
+ console.log(` bundle ${colors.dim}${bundleHash.slice(0, 16)}…${colors.reset}`);
376
392
  }
377
393
  else {
378
394
  const text = await response.text();
379
- console.error(`\n${colors.bold}${colors.red}❌ Upload failed (HTTP ${response.status}): ${text}${colors.reset}`);
395
+ console.error(` ${colors.red}error${colors.reset} upload failed (HTTP ${response.status}): ${text}`);
380
396
  process.exit(1);
381
397
  }
382
398
  }
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`);
399
+ console.log(`\n${colors.dim}${"-".repeat(48)}${colors.reset}`);
400
+ console.log(` ${colors.green}✓${colors.reset} ${colors.bold}done${colors.reset} update will be applied on next OTA sync`);
401
+ console.log(`${colors.dim}${"-".repeat(48)}${colors.reset}\n`);
387
402
  });
388
403
  // ──────────────────────────────────────────────────────
389
404
  // edge-ota status
@@ -400,23 +415,20 @@ program
400
415
  const token = options.token || process.env.EDGE_OTA_TOKEN;
401
416
  const projectId = options.project || config.projectId;
402
417
  if (!token) {
403
- console.error("❌ Auth token required. Use -t <token> or set EDGE_OTA_TOKEN.");
418
+ console.error(` ${colors.red}error${colors.reset} token required — set EDGE_OTA_TOKEN in .env or use -t`);
404
419
  process.exit(1);
405
420
  }
406
421
  const headers = { Authorization: `Bearer ${token}` };
407
- if (projectId) {
422
+ if (projectId)
408
423
  headers["x-project-id"] = projectId;
409
- }
410
- const res = await fetch(`${config.serverUrl}/api/releases`, {
411
- headers
412
- });
424
+ const res = await fetch(`${config.serverUrl}/api/releases`, { headers });
413
425
  if (!res.ok) {
414
- console.error(`❌ Failed to fetch releases (HTTP ${res.status})`);
426
+ console.error(` ${colors.red}error${colors.reset} failed to fetch releases (HTTP ${res.status})`);
415
427
  process.exit(1);
416
428
  }
417
429
  const releases = (await res.json());
418
430
  if (!releases.length) {
419
- console.log("No releases found.");
431
+ console.log(` ${colors.dim}no releases found${colors.reset}`);
420
432
  return;
421
433
  }
422
434
  const rows = releases.slice(0, parseInt(options.limit)).map((r) => ({
@@ -425,11 +437,9 @@ program
425
437
  Runtime: r.runtime,
426
438
  Status: r.status,
427
439
  Published: r.published,
428
- "By": r.publisher
440
+ By: r.publisher
429
441
  }));
430
442
  console.table(rows);
431
- console.log("\n✨ Powered by EdgeOTA — A product of Renbo Studios" +
432
- "\n👉 Need scalable app development or cloud setup? Visit us at https://renbostudios.com");
433
443
  });
434
444
  // ──────────────────────────────────────────────────────
435
445
  // edge-ota keygen
@@ -439,11 +449,14 @@ program
439
449
  .description("Generate a fresh ECDSA key pair (prints to stdout, does not write files)")
440
450
  .action(async () => {
441
451
  const keys = await generateECDSAKeyPair();
442
- console.log("--- PRIVATE KEY (keep secret) ---");
452
+ const sep = `${colors.dim}${"-".repeat(48)}${colors.reset}`;
453
+ console.log(`\n${sep}`);
454
+ console.log(` ${colors.bold}private key${colors.reset} ${colors.dim}keep secret — never commit${colors.reset}`);
455
+ console.log(sep);
443
456
  console.log(keys.privateKey);
444
- console.log("\n--- PUBLIC KEY (paste into dashboard) ---");
457
+ console.log(`\n${sep}`);
458
+ console.log(` ${colors.bold}public key${colors.reset} ${colors.dim}paste into dashboard → Settings → General${colors.reset}`);
459
+ console.log(sep);
445
460
  console.log(keys.publicKey);
446
- console.log("\n✨ Powered by EdgeOTA — A product of Renbo Studios" +
447
- "\n👉 Need scalable app development or cloud setup? Visit us at https://renbostudios.com");
448
461
  });
449
462
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renbostudios/edge-ota",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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",
@@ -8,13 +8,20 @@
8
8
  "homepage": "https://github.com/renbostudios/edge-ota",
9
9
  "repository": {
10
10
  "type": "git",
11
- "url": "https://github.com/renbostudios/edge-ota.git",
12
- "directory": "apps/cli"
11
+ "url": "https://github.com/renbostudios/edge-ota.git"
13
12
  },
14
13
  "bugs": {
15
14
  "url": "https://github.com/renbostudios/edge-ota/issues"
16
15
  },
17
- "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
+ ],
18
25
  "type": "module",
19
26
  "bin": {
20
27
  "edge-ota": "./dist/index.js"