@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.
- package/dist/index.js +106 -93
- 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
|
-
|
|
54
|
-
console.log(
|
|
55
|
-
console.log(`
|
|
56
|
-
console.log(`
|
|
57
|
-
console.log(
|
|
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(
|
|
110
|
+
console.log(` ${colors.dim}app.json${colors.reset} updates.url set`);
|
|
97
111
|
}
|
|
98
112
|
catch (error) {
|
|
99
|
-
console.error(
|
|
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(
|
|
217
|
-
const answer = await askQuestion(
|
|
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(
|
|
223
|
-
const hostedAnswer = await askQuestion(
|
|
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
|
-
|
|
232
|
-
|
|
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(
|
|
250
|
+
console.log(` ${colors.dim}generating ECDSA P-256 key pair...${colors.reset}`);
|
|
240
251
|
const keys = await generateECDSAKeyPair();
|
|
241
|
-
const config = {
|
|
242
|
-
|
|
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
|
-
|
|
270
|
-
console.log(
|
|
271
|
-
console.log(
|
|
272
|
-
console.log(
|
|
273
|
-
console.log(
|
|
274
|
-
console.log(
|
|
275
|
-
console.log(
|
|
276
|
-
console.log(
|
|
277
|
-
console.log(`${colors.
|
|
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(
|
|
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(
|
|
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(
|
|
327
|
+
console.log(` ${colors.dim}skipping expo export (--skip-export)${colors.reset}`);
|
|
313
328
|
}
|
|
314
329
|
if (!fs.existsSync(distDir)) {
|
|
315
|
-
console.error(
|
|
330
|
+
console.error(` ${colors.red}error${colors.reset} ./dist not found after export`);
|
|
316
331
|
process.exit(1);
|
|
317
332
|
}
|
|
318
|
-
|
|
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(`
|
|
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${
|
|
325
|
-
|
|
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(
|
|
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(
|
|
357
|
+
console.log(` signing ECDSA P-256`);
|
|
344
358
|
const signature = await signPayload(payloadStr, privateKey);
|
|
345
|
-
console.log(`
|
|
359
|
+
console.log(` sig ${colors.dim}${signature.slice(0, 24)}…${colors.reset}`);
|
|
346
360
|
if (options.dryRun) {
|
|
347
|
-
console.log(
|
|
348
|
-
console.log("
|
|
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(
|
|
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(
|
|
362
|
-
const response = await fetch(
|
|
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(
|
|
372
|
-
console.log(`
|
|
373
|
-
console.log(`
|
|
374
|
-
console.log(`
|
|
375
|
-
console.log(`
|
|
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(
|
|
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.
|
|
384
|
-
|
|
385
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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": [
|
|
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"
|