@renbostudios/edge-ota 0.1.0 → 0.2.0
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 +5 -2
- package/dist/index.js +84 -13
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -30,15 +30,18 @@ edge-ota status
|
|
|
30
30
|
|
|
31
31
|
### `edge-ota init [options]`
|
|
32
32
|
|
|
33
|
-
Generates an ECDSA P-256 key pair, writes `edge-ota.config.json` and `.edge-ota.private.key`,
|
|
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
|
+
|
|
35
|
+
If `-s` or `--server` is not specified, you will be prompted interactively to enter the URL of your hosted or local EdgeOTA server.
|
|
34
36
|
|
|
35
37
|
| Option | Default | Description |
|
|
36
38
|
|--------|---------|-------------|
|
|
37
|
-
| `-s, --server <url>` |
|
|
39
|
+
| `-s, --server <url>` | — | Your EdgeOTA server URL (prompts if omitted) |
|
|
38
40
|
|
|
39
41
|
**Output:**
|
|
40
42
|
- `edge-ota.config.json` — server URL + public key (safe to commit)
|
|
41
43
|
- `.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.
|
|
42
45
|
|
|
43
46
|
---
|
|
44
47
|
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,44 @@ import fs from "fs";
|
|
|
30
30
|
import path from "path";
|
|
31
31
|
import { createWriteStream } from "fs";
|
|
32
32
|
import archiver from "archiver";
|
|
33
|
+
import readline from "readline";
|
|
33
34
|
import { generateECDSAKeyPair, signPayload, sha256Hex } from "./core/index.js";
|
|
35
|
+
function askQuestion(query) {
|
|
36
|
+
const rl = readline.createInterface({
|
|
37
|
+
input: process.stdin,
|
|
38
|
+
output: process.stdout
|
|
39
|
+
});
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
rl.question(query, (answer) => {
|
|
42
|
+
rl.close();
|
|
43
|
+
resolve(answer.trim());
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function updateAppJson(cwd, serverUrl) {
|
|
48
|
+
const appJsonPath = path.resolve(cwd, "app.json");
|
|
49
|
+
if (!fs.existsSync(appJsonPath)) {
|
|
50
|
+
console.log("⚠️ app.json not found in this directory. Skipping auto-update.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const raw = fs.readFileSync(appJsonPath, "utf-8");
|
|
55
|
+
const data = JSON.parse(raw);
|
|
56
|
+
if (!data.expo) {
|
|
57
|
+
data.expo = {};
|
|
58
|
+
}
|
|
59
|
+
if (!data.expo.updates) {
|
|
60
|
+
data.expo.updates = {};
|
|
61
|
+
}
|
|
62
|
+
const updateUrl = `${serverUrl.replace(/\/$/, "")}/api/updates`;
|
|
63
|
+
data.expo.updates.url = updateUrl;
|
|
64
|
+
fs.writeFileSync(appJsonPath, JSON.stringify(data, null, 2), "utf-8");
|
|
65
|
+
console.log(`✅ Updated app.json: expo.updates.url set to "${updateUrl}"`);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error(`❌ Failed to update app.json: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
34
71
|
function loadConfig(cwd) {
|
|
35
72
|
const configPath = path.resolve(cwd, "edge-ota.config.json");
|
|
36
73
|
if (!fs.existsSync(configPath)) {
|
|
@@ -66,6 +103,7 @@ async function hashFile(filePath) {
|
|
|
66
103
|
}
|
|
67
104
|
const CONTENT_TYPES = {
|
|
68
105
|
".js": "application/javascript",
|
|
106
|
+
".hbc": "application/javascript",
|
|
69
107
|
".json": "application/json",
|
|
70
108
|
".png": "image/png",
|
|
71
109
|
".jpg": "image/jpeg",
|
|
@@ -105,19 +143,22 @@ async function collectAssets(distDir) {
|
|
|
105
143
|
}
|
|
106
144
|
// ─── Detect the JS bundle within the Expo export output ──────────────────────
|
|
107
145
|
function findBundle(distDir, platform) {
|
|
108
|
-
// Expo SDK 50+ writes: dist/_expo/static/js/<platform>/<hash>.js
|
|
146
|
+
// Expo SDK 50+ writes: dist/_expo/static/js/<platform>/<hash>.js (or .hbc)
|
|
109
147
|
const expoStaticJs = path.join(distDir, "_expo", "static", "js", platform);
|
|
110
148
|
if (fs.existsSync(expoStaticJs)) {
|
|
111
|
-
const files = fs.readdirSync(expoStaticJs).filter(f => f.endsWith(".js"));
|
|
149
|
+
const files = fs.readdirSync(expoStaticJs).filter(f => f.endsWith(".js") || f.endsWith(".hbc"));
|
|
112
150
|
if (files.length)
|
|
113
151
|
return path.join(expoStaticJs, files[0]);
|
|
114
152
|
}
|
|
115
153
|
// Fallback: older SDK flat layout
|
|
116
|
-
const
|
|
117
|
-
if (fs.existsSync(
|
|
118
|
-
return
|
|
119
|
-
|
|
120
|
-
|
|
154
|
+
const flatJs = path.join(distDir, `index.${platform}.js`);
|
|
155
|
+
if (fs.existsSync(flatJs))
|
|
156
|
+
return flatJs;
|
|
157
|
+
const flatHbc = path.join(distDir, `index.${platform}.hbc`);
|
|
158
|
+
if (fs.existsSync(flatHbc))
|
|
159
|
+
return flatHbc;
|
|
160
|
+
// Any .js or .hbc at root
|
|
161
|
+
const rootJs = fs.readdirSync(distDir).find(f => f.endsWith(".js") || f.endsWith(".hbc"));
|
|
121
162
|
if (rootJs)
|
|
122
163
|
return path.join(distDir, rootJs);
|
|
123
164
|
return null;
|
|
@@ -134,14 +175,33 @@ program
|
|
|
134
175
|
program
|
|
135
176
|
.command("init")
|
|
136
177
|
.description("Generate an ECDSA P-256 key pair and write edge-ota.config.json")
|
|
137
|
-
.option("-s, --server <url>", "EdgeOTA server URL"
|
|
178
|
+
.option("-s, --server <url>", "EdgeOTA server URL")
|
|
138
179
|
.action(async (options) => {
|
|
180
|
+
let serverUrl = options.server;
|
|
181
|
+
if (!serverUrl) {
|
|
182
|
+
console.log("ℹ️ Server URL not provided via -s/--server.");
|
|
183
|
+
const answer = await askQuestion("Enter your EdgeOTA server URL [http://localhost:3000]: ");
|
|
184
|
+
if (!answer) {
|
|
185
|
+
serverUrl = "http://localhost:3000";
|
|
186
|
+
}
|
|
187
|
+
else if (answer.toLowerCase().includes("host")) {
|
|
188
|
+
console.log("\nℹ️ Please set the server URL to your hosted EdgeOTA instance (e.g. https://ota.renbostudios.com)");
|
|
189
|
+
const hostedAnswer = await askQuestion("Hosted server URL: ");
|
|
190
|
+
serverUrl = hostedAnswer.trim() || "http://localhost:3000";
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
serverUrl = answer;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Normalise URL (remove trailing slashes)
|
|
197
|
+
serverUrl = serverUrl.replace(/\/$/, "");
|
|
139
198
|
console.log("⚙️ Generating ECDSA P-256 key pair...");
|
|
140
199
|
const keys = await generateECDSAKeyPair();
|
|
141
200
|
const config = {
|
|
142
|
-
serverUrl:
|
|
201
|
+
serverUrl: serverUrl,
|
|
143
202
|
publicKey: keys.publicKey
|
|
144
203
|
};
|
|
204
|
+
const cwd = process.cwd();
|
|
145
205
|
fs.writeFileSync("edge-ota.config.json", JSON.stringify(config, null, 2));
|
|
146
206
|
fs.writeFileSync(".edge-ota.private.key", keys.privateKey, { mode: 0o600 });
|
|
147
207
|
// Add the private key to .gitignore if it isn't already
|
|
@@ -157,15 +217,20 @@ program
|
|
|
157
217
|
else {
|
|
158
218
|
fs.writeFileSync(gitignorePath, `${gitignoreEntry}\n`);
|
|
159
219
|
}
|
|
220
|
+
// Auto-update app.json updates.url
|
|
221
|
+
updateAppJson(cwd, serverUrl);
|
|
160
222
|
console.log("\n✅ EdgeOTA initialised.");
|
|
161
223
|
console.log(" Config : edge-ota.config.json");
|
|
162
224
|
console.log(" Key : .edge-ota.private.key (keep this secret!)");
|
|
163
225
|
console.log("\n📋 Public key to paste into your dashboard → Settings → Infrastructure:\n");
|
|
164
226
|
console.log(keys.publicKey);
|
|
165
|
-
console.log("\n💡
|
|
227
|
+
console.log("\n💡 Your app.json updates configuration has been configured to:\n" +
|
|
166
228
|
' "updates": {\n' +
|
|
167
|
-
' "url": "' +
|
|
168
|
-
" }"
|
|
229
|
+
' "url": "' + serverUrl + '/api/updates"\n' +
|
|
230
|
+
" }\n" +
|
|
231
|
+
"\n✨ Powered by EdgeOTA — A product of Renbo Studios" +
|
|
232
|
+
"\n🚀 Need scalable app development or custom Cloudflare setup?" +
|
|
233
|
+
"\n👉 Partner with us at https://renbostudios.com to launch your next project!");
|
|
169
234
|
});
|
|
170
235
|
// ──────────────────────────────────────────────────────
|
|
171
236
|
// edge-ota push
|
|
@@ -272,7 +337,9 @@ program
|
|
|
272
337
|
}
|
|
273
338
|
}
|
|
274
339
|
console.log("\n🎉 Done. Your app will receive this update on the next OTA sync.\n" +
|
|
275
|
-
"
|
|
340
|
+
"\n✨ Powered by EdgeOTA — A product of Renbo Studios" +
|
|
341
|
+
"\n🚀 Need scalable app development or custom Cloudflare setup?" +
|
|
342
|
+
"\n👉 Partner with us at https://renbostudios.com to launch your next project!");
|
|
276
343
|
});
|
|
277
344
|
// ──────────────────────────────────────────────────────
|
|
278
345
|
// edge-ota status
|
|
@@ -311,6 +378,8 @@ program
|
|
|
311
378
|
"By": r.publisher
|
|
312
379
|
}));
|
|
313
380
|
console.table(rows);
|
|
381
|
+
console.log("\n✨ Powered by EdgeOTA — A product of Renbo Studios" +
|
|
382
|
+
"\n👉 Need scalable app development or cloud setup? Visit us at https://renbostudios.com");
|
|
314
383
|
});
|
|
315
384
|
// ──────────────────────────────────────────────────────
|
|
316
385
|
// edge-ota keygen
|
|
@@ -324,5 +393,7 @@ program
|
|
|
324
393
|
console.log(keys.privateKey);
|
|
325
394
|
console.log("\n--- PUBLIC KEY (paste into dashboard) ---");
|
|
326
395
|
console.log(keys.publicKey);
|
|
396
|
+
console.log("\n✨ Powered by EdgeOTA — A product of Renbo Studios" +
|
|
397
|
+
"\n👉 Need scalable app development or cloud setup? Visit us at https://renbostudios.com");
|
|
327
398
|
});
|
|
328
399
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@renbostudios/edge-ota",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"preferGlobal": true,
|
|
4
5
|
"description": "Zero-SDK, self-hostable OTA update CLI for Expo — push signed JS bundles to any EdgeOTA server",
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"author": "EdgeOTA Contributors",
|