@renbostudios/edge-ota 0.2.8 → 0.2.9
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 +162 -38
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -146,7 +146,7 @@ const spin = (() => {
|
|
|
146
146
|
};
|
|
147
147
|
})();
|
|
148
148
|
// ─── Package version ─────────────────────────────────────────────────────────
|
|
149
|
-
let packageVersion = "0.2.
|
|
149
|
+
let packageVersion = "0.2.9";
|
|
150
150
|
try {
|
|
151
151
|
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
152
152
|
if (fs.existsSync(pkgPath)) {
|
|
@@ -284,6 +284,75 @@ function askSecret(query) {
|
|
|
284
284
|
}
|
|
285
285
|
});
|
|
286
286
|
}
|
|
287
|
+
function selectOption(question, options) {
|
|
288
|
+
return new Promise((resolve) => {
|
|
289
|
+
if (!process.stdin.isTTY) {
|
|
290
|
+
console.log(` ${c.bold}${question}${c.reset}`);
|
|
291
|
+
options.forEach((opt, idx) => {
|
|
292
|
+
console.log(` [${idx + 1}] ${opt.label}`);
|
|
293
|
+
});
|
|
294
|
+
ask(` selection (1-${options.length}): `).then((choice) => {
|
|
295
|
+
const idx = parseInt(choice) - 1;
|
|
296
|
+
if (!isNaN(idx) && idx >= 0 && idx < options.length) {
|
|
297
|
+
resolve(options[idx].value);
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
resolve(options[0].value);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
let cursor = 0;
|
|
306
|
+
const hideCursor = "\u001b[?25l";
|
|
307
|
+
const showCursor = "\u001b[?25h";
|
|
308
|
+
let hasRendered = false;
|
|
309
|
+
console.log(` ${c.bold}${question}${c.reset}`);
|
|
310
|
+
function render() {
|
|
311
|
+
if (hasRendered) {
|
|
312
|
+
for (let i = 0; i < options.length; i++) {
|
|
313
|
+
process.stdout.write("\r\u001b[K\u001b[A");
|
|
314
|
+
}
|
|
315
|
+
process.stdout.write("\r\u001b[K");
|
|
316
|
+
}
|
|
317
|
+
hasRendered = true;
|
|
318
|
+
options.forEach((opt, idx) => {
|
|
319
|
+
const isSelected = idx === cursor;
|
|
320
|
+
const marker = isSelected ? `${c.cyan}❯${c.reset}` : " ";
|
|
321
|
+
const text = isSelected ? `${c.bold}${c.cyan}${opt.label}${c.reset}` : `${c.dim}${opt.label}${c.reset}`;
|
|
322
|
+
process.stdout.write(` ${marker} ${text}\n`);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
readline.emitKeypressEvents(process.stdin);
|
|
326
|
+
process.stdin.setRawMode(true);
|
|
327
|
+
process.stdin.resume();
|
|
328
|
+
process.stdout.write(hideCursor);
|
|
329
|
+
render();
|
|
330
|
+
function onKeypress(str, key) {
|
|
331
|
+
if (key.ctrl && key.name === "c") {
|
|
332
|
+
process.stdout.write(showCursor);
|
|
333
|
+
process.stdin.setRawMode(false);
|
|
334
|
+
process.stdin.pause();
|
|
335
|
+
process.exit(0);
|
|
336
|
+
}
|
|
337
|
+
if (key.name === "up" || key.name === "k") {
|
|
338
|
+
cursor = (cursor - 1 + options.length) % options.length;
|
|
339
|
+
render();
|
|
340
|
+
}
|
|
341
|
+
else if (key.name === "down" || key.name === "j") {
|
|
342
|
+
cursor = (cursor + 1) % options.length;
|
|
343
|
+
render();
|
|
344
|
+
}
|
|
345
|
+
else if (key.name === "return" || key.name === "enter") {
|
|
346
|
+
process.stdout.write(showCursor);
|
|
347
|
+
process.stdin.setRawMode(false);
|
|
348
|
+
process.stdin.removeListener("keypress", onKeypress);
|
|
349
|
+
process.stdin.pause();
|
|
350
|
+
resolve(options[cursor].value);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
process.stdin.on("keypress", onKeypress);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
287
356
|
const CONTENT_TYPES = {
|
|
288
357
|
".js": "application/javascript",
|
|
289
358
|
".hbc": "application/javascript",
|
|
@@ -460,51 +529,106 @@ program
|
|
|
460
529
|
const globalCfg = loadGlobalConfig();
|
|
461
530
|
const serverUrl = (options.server || globalCfg?.serverUrl || "https://api.edge-ota.renbo.site").replace(/\/$/, "");
|
|
462
531
|
const cwd = process.cwd();
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const data = JSON.parse(fs.readFileSync(path.resolve(cwd, "app.json"), "utf-8"));
|
|
467
|
-
suggestedName = data?.expo?.name || suggestedName;
|
|
468
|
-
}
|
|
469
|
-
catch { /* ignore */ }
|
|
470
|
-
const projectName = (await ask(` ${c.dim}project name [${suggestedName}]:${c.reset} `)) || suggestedName;
|
|
471
|
-
spin.start("generating ECDSA P-256 key pair");
|
|
472
|
-
const keys = await generateECDSAKeyPair();
|
|
473
|
-
spin.stop("key pair generated");
|
|
474
|
-
spin.start("registering project on server");
|
|
475
|
-
let projectId;
|
|
532
|
+
// Fetch existing projects
|
|
533
|
+
spin.start("fetching existing projects from server");
|
|
534
|
+
let projects = [];
|
|
476
535
|
try {
|
|
477
536
|
const res = await fetch(`${serverUrl}/api/projects`, {
|
|
478
|
-
|
|
479
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
480
|
-
body: JSON.stringify({ name: projectName, publicKey: keys.publicKey }),
|
|
537
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
481
538
|
});
|
|
482
|
-
if (
|
|
483
|
-
|
|
484
|
-
spin.fail(`Failed to register project: ${text}`);
|
|
485
|
-
process.exit(1);
|
|
539
|
+
if (res.ok) {
|
|
540
|
+
projects = await res.json();
|
|
486
541
|
}
|
|
487
|
-
|
|
488
|
-
projectId = data.id;
|
|
489
|
-
spin.stop("project registered");
|
|
542
|
+
spin.stop();
|
|
490
543
|
}
|
|
491
544
|
catch (e) {
|
|
492
|
-
spin.fail(`
|
|
493
|
-
|
|
545
|
+
spin.fail(`Failed to fetch projects: ${e.message}`);
|
|
546
|
+
// Continue with creation if list fails
|
|
547
|
+
}
|
|
548
|
+
let projectId = null;
|
|
549
|
+
let projectName = "";
|
|
550
|
+
if (projects.length > 0) {
|
|
551
|
+
const options = projects.map(p => ({
|
|
552
|
+
label: `${p.name} ${c.dim}(id: ${p.id.slice(0, 8)}...)${c.reset}`,
|
|
553
|
+
value: p
|
|
554
|
+
}));
|
|
555
|
+
options.push({
|
|
556
|
+
label: `${c.green}Create a new project...${c.reset}`,
|
|
557
|
+
value: null
|
|
558
|
+
});
|
|
559
|
+
const selected = await selectOption("Select a project to associate with this app:", options);
|
|
560
|
+
if (selected) {
|
|
561
|
+
projectId = selected.id;
|
|
562
|
+
projectName = selected.name;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const keys = await generateECDSAKeyPair();
|
|
566
|
+
if (projectId) {
|
|
567
|
+
// Re-initialize existing project: update public key on the server
|
|
568
|
+
spin.start(`associating project "${projectName}"`);
|
|
569
|
+
try {
|
|
570
|
+
const res = await fetch(`${serverUrl}/api/projects/${projectId}`, {
|
|
571
|
+
method: "PUT",
|
|
572
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
573
|
+
body: JSON.stringify({ name: projectName, publicKey: keys.publicKey }),
|
|
574
|
+
});
|
|
575
|
+
if (!res.ok) {
|
|
576
|
+
const text = await res.text();
|
|
577
|
+
spin.fail(`Failed to update project public key on server: ${text}`);
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
spin.stop(`associated project "${projectName}"`);
|
|
581
|
+
}
|
|
582
|
+
catch (e) {
|
|
583
|
+
spin.fail(`Connection error: ${e.message}`);
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
// Create new project
|
|
589
|
+
let suggestedName = path.basename(cwd);
|
|
590
|
+
try {
|
|
591
|
+
const data = JSON.parse(fs.readFileSync(path.resolve(cwd, "app.json"), "utf-8"));
|
|
592
|
+
suggestedName = data?.expo?.name || suggestedName;
|
|
593
|
+
}
|
|
594
|
+
catch { /* ignore */ }
|
|
595
|
+
projectName = (await ask(` ${c.dim}project name [${suggestedName}]:${c.reset} `)) || suggestedName;
|
|
596
|
+
spin.start("registering project on server");
|
|
597
|
+
try {
|
|
598
|
+
const res = await fetch(`${serverUrl}/api/projects`, {
|
|
599
|
+
method: "POST",
|
|
600
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
601
|
+
body: JSON.stringify({ name: projectName, publicKey: keys.publicKey }),
|
|
602
|
+
});
|
|
603
|
+
if (!res.ok) {
|
|
604
|
+
const text = await res.text();
|
|
605
|
+
spin.fail(`Failed to register project: ${text}`);
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
const data = await res.json();
|
|
609
|
+
projectId = data.id;
|
|
610
|
+
spin.stop("project registered");
|
|
611
|
+
}
|
|
612
|
+
catch (e) {
|
|
613
|
+
spin.fail(`Connection error: ${e.message}`);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
494
616
|
}
|
|
495
617
|
// Save private key globally
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
618
|
+
if (projectId) {
|
|
619
|
+
savePrivateKey(projectId, keys.privateKey);
|
|
620
|
+
// Update app.json
|
|
621
|
+
updateAppJson(cwd, serverUrl, projectId, keys.publicKey);
|
|
622
|
+
const sep = `${c.dim}${"─".repeat(52)}${c.reset}`;
|
|
623
|
+
console.log(`\n${sep}`);
|
|
624
|
+
console.log(` ${c.green}✓${c.reset} ${c.bold}Initialised${c.reset}`);
|
|
625
|
+
console.log(sep);
|
|
626
|
+
console.log(` project ${c.dim}${projectId}${c.reset}`);
|
|
627
|
+
console.log(` server ${c.dim}${serverUrl}${c.reset}`);
|
|
628
|
+
console.log(` key file ${c.dim}${path.join(GLOBAL_KEYS_DIR, `${projectId}.key`)}${c.reset}`);
|
|
629
|
+
console.log(sep + "\n");
|
|
630
|
+
console.log(` ${c.dim}Next step:${c.reset} run ${c.cyan}edge-ota push${c.reset} to publish your first update.\n`);
|
|
631
|
+
}
|
|
508
632
|
});
|
|
509
633
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
510
634
|
// edge-ota push
|
package/package.json
CHANGED