@renbostudios/edge-ota 0.2.9 → 0.3.1

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 +94 -11
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * edge-ota keygen — Generate a new ECDSA key pair (prints to stdout)
14
14
  */
15
15
  import { Command } from "commander";
16
- import { spawn } from "child_process";
16
+ import { execSync, spawn } from "child_process";
17
17
  import fs from "fs";
18
18
  import os from "os";
19
19
  import path from "path";
@@ -146,7 +146,7 @@ const spin = (() => {
146
146
  };
147
147
  })();
148
148
  // ─── Package version ─────────────────────────────────────────────────────────
149
- let packageVersion = "0.2.9";
149
+ let packageVersion = "0.3.0";
150
150
  try {
151
151
  const pkgPath = path.join(__dirname, "..", "package.json");
152
152
  if (fs.existsSync(pkgPath)) {
@@ -154,6 +154,70 @@ try {
154
154
  }
155
155
  }
156
156
  catch { /* fallback */ }
157
+ // ─── Auto Update Check ────────────────────────────────────────────────────────
158
+ const UPDATE_CHECK_FILE = path.join(GLOBAL_CONFIG_DIR, "update-check.json");
159
+ function isNewerVersion(current, latest) {
160
+ const cParts = current.split(".").map(Number);
161
+ const lParts = latest.split(".").map(Number);
162
+ for (let i = 0; i < 3; i++) {
163
+ const cNum = cParts[i] || 0;
164
+ const lNum = lParts[i] || 0;
165
+ if (lNum > cNum)
166
+ return true;
167
+ if (lNum < cNum)
168
+ return false;
169
+ }
170
+ return false;
171
+ }
172
+ async function checkAndAutoUpdate() {
173
+ if (process.env.EDGE_OTA_UPDATING === "true")
174
+ return;
175
+ try {
176
+ const now = Date.now();
177
+ if (fs.existsSync(UPDATE_CHECK_FILE)) {
178
+ try {
179
+ const cache = JSON.parse(fs.readFileSync(UPDATE_CHECK_FILE, "utf-8"));
180
+ // Only check npm once every 15 minutes to keep CLI commands extremely fast
181
+ if (now - (cache.lastChecked || 0) < 15 * 60 * 1000) {
182
+ return;
183
+ }
184
+ }
185
+ catch { /* ignore */ }
186
+ }
187
+ // Save timestamp immediately to prevent concurrent requests
188
+ fs.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
189
+ fs.writeFileSync(UPDATE_CHECK_FILE, JSON.stringify({ lastChecked: now }));
190
+ // Fetch latest version from registry with a short 1s timeout
191
+ const controller = new AbortController();
192
+ const timerId = setTimeout(() => controller.abort(), 1000);
193
+ const res = await fetch("https://registry.npmjs.org/@renbostudios/edge-ota/latest", {
194
+ signal: controller.signal
195
+ });
196
+ clearTimeout(timerId);
197
+ if (!res.ok)
198
+ return;
199
+ const data = (await res.json());
200
+ const latestVersion = data.version;
201
+ if (latestVersion && isNewerVersion(packageVersion, latestVersion)) {
202
+ console.log(`\n ${c.yellow}${c.reset} New version of ${c.bold}edge-ota${c.reset} available: ${c.green}${latestVersion}${c.reset} (current: ${c.dim}${packageVersion}${c.reset})`);
203
+ console.log(` ${c.blue}➜${c.reset} Auto-updating globally...`);
204
+ try {
205
+ execSync("npm install -g @renbostudios/edge-ota", {
206
+ stdio: "inherit",
207
+ env: { ...process.env, EDGE_OTA_UPDATING: "true" }
208
+ });
209
+ console.log(` ${c.green}✓${c.reset} Successfully auto-updated to ${c.bold}${latestVersion}${c.reset}!\n`);
210
+ }
211
+ catch (err) {
212
+ console.error(` ${c.red}✗${c.reset} Auto-update failed: ${err.message}`);
213
+ console.error(` Please update manually: ${c.cyan}npm install -g @renbostudios/edge-ota${c.reset}\n`);
214
+ }
215
+ }
216
+ }
217
+ catch {
218
+ // Fail silently so CLI works offline
219
+ }
220
+ }
157
221
  // ─── Banner ───────────────────────────────────────────────────────────────────
158
222
  function printBanner() {
159
223
  const sep = `${c.dim}${"─".repeat(52)}${c.reset}`;
@@ -163,6 +227,8 @@ function printBanner() {
163
227
  console.log(` ${c.dim}renbostudios.com${c.reset}`);
164
228
  console.log(`${sep}\n`);
165
229
  }
230
+ // ─── app.json helpers ────────────────────────────────────────────────────────
231
+ const DEFAULT_SERVER = "https://api.ota.renbo.site";
166
232
  function readAppJson(cwd) {
167
233
  const appJsonPath = path.resolve(cwd, "app.json");
168
234
  if (!fs.existsSync(appJsonPath)) {
@@ -193,7 +259,7 @@ function readAppJson(cwd) {
193
259
  console.error(` Run ${c.cyan}edge-ota init${c.reset} to set this up.\n`);
194
260
  process.exit(1);
195
261
  }
196
- // Parse serverUrl and projectId from the updates URL
262
+ // Parse serverUrl and projectId from the updates URL.
197
263
  // Expected format: https://<host>/api/projects/<uuid>/updates
198
264
  // or: https://<host>/api/updates
199
265
  let serverUrl;
@@ -213,7 +279,14 @@ function readAppJson(cwd) {
213
279
  console.error(` ${c.red}✗${c.reset} Could not parse ${c.dim}expo.updates.url${c.reset} in app.json`);
214
280
  process.exit(1);
215
281
  }
216
- return { serverUrl, projectId, runtimeVersion, publicKey: expo.updates?.publicKey };
282
+ // ── Project-level server override ──
283
+ // expo.extra.edgeOtaServer lets users point a specific project at a
284
+ // different (self-hosted) EdgeOTA instance without re-logging in.
285
+ const projectServer = expo?.extra?.edgeOtaServer;
286
+ if (projectServer) {
287
+ serverUrl = projectServer.replace(/\/$/, "");
288
+ }
289
+ return { serverUrl, projectId, runtimeVersion, publicKey: expo?.extra?.edgeOtaPublicKey };
217
290
  }
218
291
  function updateAppJson(cwd, serverUrl, projectId, publicKey) {
219
292
  const appJsonPath = path.resolve(cwd, "app.json");
@@ -225,6 +298,8 @@ function updateAppJson(cwd, serverUrl, projectId, publicKey) {
225
298
  data.expo = {};
226
299
  if (!data.expo.updates)
227
300
  data.expo.updates = {};
301
+ if (!data.expo.extra)
302
+ data.expo.extra = {};
228
303
  const cleanUrl = serverUrl.replace(/\/$/, "");
229
304
  data.expo.updates.url = projectId
230
305
  ? `${cleanUrl}/api/projects/${projectId}/updates`
@@ -232,13 +307,15 @@ function updateAppJson(cwd, serverUrl, projectId, publicKey) {
232
307
  data.expo.updates.checkAutomatically = data.expo.updates.checkAutomatically ?? "ON_LOAD";
233
308
  data.expo.updates.fallbackToCacheTimeout = data.expo.updates.fallbackToCacheTimeout ?? 30000;
234
309
  data.expo.updates.requestHeaders = data.expo.updates.requestHeaders ?? { "expo-channel-name": "production" };
310
+ // Store the server URL in expo.extra.edgeOtaServer so users can easily
311
+ // override which EdgeOTA instance this project points to without re-running
312
+ // `edge-ota login`. They can edit this value directly in app.json.
313
+ data.expo.extra.edgeOtaServer = cleanUrl;
235
314
  if (publicKey) {
236
- data.expo.updates.codeSigningCertificate = undefined; // clear legacy
237
- data.expo.extra = data.expo.extra ?? {};
238
315
  data.expo.extra.edgeOtaPublicKey = publicKey;
239
316
  }
240
317
  fs.writeFileSync(appJsonPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
241
- console.log(` ${c.dim}app.json${c.reset} updated ${c.dim}expo.updates.url${c.reset}`);
318
+ console.log(` ${c.dim}app.json${c.reset} updated ${c.dim}expo.updates.url${c.reset} + ${c.dim}expo.extra.edgeOtaServer${c.reset}`);
242
319
  }
243
320
  catch (e) {
244
321
  console.error(` ${c.yellow}⚠${c.reset} Could not update app.json: ${e.message}`);
@@ -462,11 +539,16 @@ program.action(() => {
462
539
  program
463
540
  .command("login")
464
541
  .description("Authenticate with your EdgeOTA account")
465
- .option("-s, --server <url>", "EdgeOTA server URL", "https://api.edge-ota.renbo.site")
542
+ .option("-s, --server <url>", `EdgeOTA server URL (default: ${DEFAULT_SERVER})`)
466
543
  .action(async (options) => {
467
544
  printBanner();
468
- console.log(` ${c.bold}Sign in to EdgeOTA${c.reset}\n`);
469
- const serverUrl = options.server.replace(/\/$/, "");
545
+ const usingCustomServer = options.server && options.server !== DEFAULT_SERVER;
546
+ console.log(` ${c.bold}Sign in to EdgeOTA${c.reset}`);
547
+ if (usingCustomServer) {
548
+ console.log(` ${c.dim}server: ${options.server}${c.reset}`);
549
+ }
550
+ console.log();
551
+ const serverUrl = (options.server || DEFAULT_SERVER).replace(/\/$/, "");
470
552
  const email = await ask(` ${c.dim}email:${c.reset} `);
471
553
  const password = await askSecret(` ${c.dim}password:${c.reset} `);
472
554
  spin.start("authenticating");
@@ -527,7 +609,7 @@ program
527
609
  process.exit(1);
528
610
  }
529
611
  const globalCfg = loadGlobalConfig();
530
- const serverUrl = (options.server || globalCfg?.serverUrl || "https://api.edge-ota.renbo.site").replace(/\/$/, "");
612
+ const serverUrl = (options.server || globalCfg?.serverUrl || DEFAULT_SERVER).replace(/\/$/, "");
531
613
  const cwd = process.cwd();
532
614
  // Fetch existing projects
533
615
  spin.start("fetching existing projects from server");
@@ -851,4 +933,5 @@ program
851
933
  console.log(sep);
852
934
  console.log(keys.publicKey);
853
935
  });
936
+ await checkAndAutoUpdate();
854
937
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renbostudios/edge-ota",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
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",