@orb44/cli 0.1.4 → 0.1.5
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 +7 -4
- package/bin/orb44.mjs +67 -15
- package/package.json +3 -2
- package/server/pulse.mjs +37 -13
- package/server/sat-i18n.mjs +32 -0
- package/server/sat-update.mjs +104 -0
package/README.md
CHANGED
|
@@ -13,10 +13,13 @@ npx @orb44/cli login --url https://orb44.com
|
|
|
13
13
|
Open the printed link in a browser where you are already signed in, confirm the domain, then optionally install the systemd daemon (default no). After a global install the command is `orb44`.
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npx @orb44/cli status
|
|
17
|
-
npx @orb44/cli pulse
|
|
18
|
-
npx @orb44/cli install # later: background pulse, starts after reboot
|
|
19
|
-
|
|
16
|
+
npx @orb44/cli@0.1.5 status
|
|
17
|
+
npx @orb44/cli@0.1.5 pulse
|
|
18
|
+
npx @orb44/cli@0.1.5 install # later: background pulse, starts after reboot
|
|
19
|
+
orb44 update # after install: replace /usr/lib/orb44-sat from npm
|
|
20
|
+
npx @orb44/cli@0.1.5 logout
|
|
20
21
|
```
|
|
21
22
|
|
|
23
|
+
Do not run bare `npx @orb44/cli` — it can reuse an old cache. Pin the version or use `orb44` after install.
|
|
24
|
+
|
|
22
25
|
Device key: `~/.config/orb44/device.json` (mode 0600). Not a shell token.
|
package/bin/orb44.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { collectPulse, formatPulsePreview } from "../server/pulse.mjs";
|
|
|
9
9
|
import { LANGS, LANG_LABEL, detectLang, normalizeLang, t } from "../server/sat-i18n.mjs";
|
|
10
10
|
import { pickFromList } from "../server/cli-menu.mjs";
|
|
11
11
|
import { SYSTEM_DEVICE, parseDeviceJson, hasExistingInstall, pickLiveDevice, deviceSearchPaths, loadFirstDevice, stripDevicePath } from "../server/sat-local.mjs";
|
|
12
|
+
import { CLI_PACKAGE, cmpVer, readCliVersion, pickSatRoots, applyUpdateTree, fetchLatestMeta, unpackTarball } from "../server/sat-update.mjs";
|
|
12
13
|
|
|
13
14
|
const DEVICE_FILE = process.env.ORB44_DEVICE_FILE || path.join(os.homedir(), ".config", "orb44", "device.json");
|
|
14
15
|
const CLI_FILE = process.env.ORB44_CLI_FILE || path.join(path.dirname(DEVICE_FILE), "cli.json");
|
|
@@ -422,25 +423,19 @@ function linkSystemCli(node, script) {
|
|
|
422
423
|
fs.writeFileSync(CLI_LINK, body, { mode: 0o755 });
|
|
423
424
|
}
|
|
424
425
|
|
|
425
|
-
function satServerDir() {
|
|
426
|
-
return path.join(path.dirname(SCRIPT), "..", "server");
|
|
427
|
-
}
|
|
428
|
-
|
|
429
426
|
function installSystemTree() {
|
|
430
427
|
const lib = "/usr/lib/orb44-sat";
|
|
431
|
-
const
|
|
428
|
+
const srcRoot = path.join(path.dirname(SCRIPT), "..");
|
|
432
429
|
fs.mkdirSync(path.join(lib, "bin"), { recursive: true, mode: 0o755 });
|
|
433
430
|
fs.mkdirSync(path.join(lib, "server"), { recursive: true, mode: 0o755 });
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
fs.copyFileSync(from, to);
|
|
443
|
-
fs.chmodSync(to, 0o644);
|
|
431
|
+
applyUpdateTree(srcRoot, lib);
|
|
432
|
+
const ver = readCliVersion(SCRIPT);
|
|
433
|
+
if (ver && ver !== "0.0.0") {
|
|
434
|
+
fs.writeFileSync(
|
|
435
|
+
path.join(lib, "package.json"),
|
|
436
|
+
JSON.stringify({ name: CLI_PACKAGE, version: ver, type: "module", private: true }, null, 2) + "\n",
|
|
437
|
+
{ mode: 0o644 }
|
|
438
|
+
);
|
|
444
439
|
}
|
|
445
440
|
return path.join(lib, "bin", "orb44.mjs");
|
|
446
441
|
}
|
|
@@ -640,6 +635,62 @@ async function cmdUninstall(opts) {
|
|
|
640
635
|
console.log(dim(t(lang, "uninstall_key_kept", { file: SYSTEM_DEVICE })));
|
|
641
636
|
}
|
|
642
637
|
|
|
638
|
+
async function cmdUpdate() {
|
|
639
|
+
applyLang(opts);
|
|
640
|
+
const current = readCliVersion(SCRIPT);
|
|
641
|
+
let meta;
|
|
642
|
+
try {
|
|
643
|
+
meta = await fetchLatestMeta();
|
|
644
|
+
} catch (e) {
|
|
645
|
+
console.error("⚠️ " + t(lang, "update_fail", { err: `: ${String(e.message || e).slice(0, 160)}` }));
|
|
646
|
+
process.exit(1);
|
|
647
|
+
}
|
|
648
|
+
const pin = t(lang, "update_npx", { version: meta.version });
|
|
649
|
+
if (cmpVer(current, meta.version) >= 0 && !opts.force) {
|
|
650
|
+
console.log("✅ " + t(lang, "update_same", { version: current }));
|
|
651
|
+
console.log(dim(pin));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
const roots = pickSatRoots(SCRIPT);
|
|
655
|
+
if (!roots.length) {
|
|
656
|
+
console.error("⚠️ " + t(lang, "update_no_tree"));
|
|
657
|
+
console.log(dim(pin));
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "orb44-upd-"));
|
|
661
|
+
let wrote = [];
|
|
662
|
+
try {
|
|
663
|
+
const pkg = await unpackTarball(meta.tarball, tmp);
|
|
664
|
+
const blocked = [];
|
|
665
|
+
for (const root of roots) {
|
|
666
|
+
try {
|
|
667
|
+
applyUpdateTree(pkg, root);
|
|
668
|
+
wrote.push(root);
|
|
669
|
+
} catch (e) {
|
|
670
|
+
blocked.push({ root, err: String(e.message || e).slice(0, 160) });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (!wrote.length) {
|
|
674
|
+
console.error("⚠️ " + t(lang, blocked.some((b) => /EACCES|permission/i.test(b.err)) ? "update_need_root" : "update_fail", { err: blocked[0]?.err ? `: ${blocked[0].err}` : "" }));
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
} finally {
|
|
678
|
+
try {
|
|
679
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
680
|
+
} catch {
|
|
681
|
+
/* tmp */
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
for (const root of wrote) {
|
|
685
|
+
console.log("✅ " + t(lang, "update_wrote", { version: meta.version, path: root }));
|
|
686
|
+
}
|
|
687
|
+
if (wrote.includes("/usr/lib/orb44-sat") && serviceIsActive(true)) {
|
|
688
|
+
spawnSync("systemctl", ["try-restart", "orb44-satellite"], { encoding: "utf8" });
|
|
689
|
+
console.log(dim(t(lang, "update_restarted")));
|
|
690
|
+
}
|
|
691
|
+
console.log(dim(pin));
|
|
692
|
+
}
|
|
693
|
+
|
|
643
694
|
function cmdStatus() {
|
|
644
695
|
applyLang(opts);
|
|
645
696
|
const device = loadDevice();
|
|
@@ -708,6 +759,7 @@ const run = {
|
|
|
708
759
|
install: () => cmdInstall(opts, { enable: true }),
|
|
709
760
|
uninstall: () => cmdUninstall(opts),
|
|
710
761
|
status: cmdStatus,
|
|
762
|
+
update: cmdUpdate,
|
|
711
763
|
logout: cmdLogout,
|
|
712
764
|
lang: () => cmdLang(opts),
|
|
713
765
|
help,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orb44/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Orb44: street snapshot of your site. This CLI is the Watch satellite — pulse from the host (load, listeners, hardening). The cabinet does not run commands on the machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"server/pulse.mjs",
|
|
12
12
|
"server/sat-i18n.mjs",
|
|
13
13
|
"server/cli-menu.mjs",
|
|
14
|
-
"server/sat-local.mjs"
|
|
14
|
+
"server/sat-local.mjs",
|
|
15
|
+
"server/sat-update.mjs"
|
|
15
16
|
],
|
|
16
17
|
"engines": {
|
|
17
18
|
"node": ">=18"
|
package/server/pulse.mjs
CHANGED
|
@@ -174,11 +174,32 @@ function topTable() {
|
|
|
174
174
|
|
|
175
175
|
function okListenAddr(a) {
|
|
176
176
|
const s = String(a || "");
|
|
177
|
-
if (s === "0.0.0.0" || s === "::" || s === "::1") return true;
|
|
177
|
+
if (s === "0.0.0.0" || s === "::" || s === "::1" || s === "*") return true;
|
|
178
178
|
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(s)) return true;
|
|
179
179
|
return /^[0-9a-fA-F:]+$/.test(s) && s.includes(":");
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
export function collapseListen(rows = []) {
|
|
183
|
+
const groups = new Map();
|
|
184
|
+
for (const r of rows) {
|
|
185
|
+
const key = `${Number(r.port)}|${r.comm || ""}`;
|
|
186
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
187
|
+
groups.get(key).push(r);
|
|
188
|
+
}
|
|
189
|
+
const out = [];
|
|
190
|
+
for (const list of groups.values()) {
|
|
191
|
+
const addrs = new Set(list.map((x) => x.addr));
|
|
192
|
+
const rest = list.filter((x) => x.addr !== "0.0.0.0" && x.addr !== "::" && x.addr !== "*");
|
|
193
|
+
if (addrs.has("*") || (addrs.has("0.0.0.0") && addrs.has("::"))) {
|
|
194
|
+
out.push({ addr: "*", port: list[0].port, comm: list[0].comm });
|
|
195
|
+
out.push(...rest);
|
|
196
|
+
} else {
|
|
197
|
+
out.push(...list);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out;
|
|
201
|
+
}
|
|
202
|
+
|
|
182
203
|
export function sanitizePulse(raw = {}) {
|
|
183
204
|
const top = Array.isArray(raw.top)
|
|
184
205
|
? raw.top
|
|
@@ -190,16 +211,18 @@ export function sanitizePulse(raw = {}) {
|
|
|
190
211
|
}))
|
|
191
212
|
.filter((r) => r.comm)
|
|
192
213
|
: [];
|
|
193
|
-
const listen =
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
214
|
+
const listen = collapseListen(
|
|
215
|
+
Array.isArray(raw.listen)
|
|
216
|
+
? raw.listen
|
|
217
|
+
.slice(0, 40)
|
|
218
|
+
.map((r) => ({
|
|
219
|
+
addr: String(r.addr || "").slice(0, 45),
|
|
220
|
+
port: Number(r.port) || 0,
|
|
221
|
+
comm: sanitizeComm(r.comm),
|
|
222
|
+
}))
|
|
223
|
+
.filter((r) => r.port > 0 && r.port < 65536 && okListenAddr(r.addr))
|
|
224
|
+
: []
|
|
225
|
+
);
|
|
203
226
|
const originA = Array.isArray(raw.originA)
|
|
204
227
|
? raw.originA.map((x) => String(x || "")).filter((x) => /^\d{1,3}(\.\d{1,3}){3}$/.test(x)).slice(0, 8)
|
|
205
228
|
: [];
|
|
@@ -710,9 +733,10 @@ export function formatPulsePreview(pulse, lang = "en") {
|
|
|
710
733
|
[t(lang, "preview_row_grade"), grade],
|
|
711
734
|
]
|
|
712
735
|
);
|
|
713
|
-
const listenRows = [...(pulse.listen || [])]
|
|
736
|
+
const listenRows = collapseListen([...(pulse.listen || [])])
|
|
714
737
|
.sort((a, b) => {
|
|
715
|
-
const rank = (addr) =>
|
|
738
|
+
const rank = (addr) =>
|
|
739
|
+
addr === "0.0.0.0" || addr === "::" || addr === "*" ? 0 : String(addr).startsWith("127.") || addr === "::1" ? 2 : 1;
|
|
716
740
|
return rank(a.addr) - rank(b.addr) || Number(a.port) - Number(b.port);
|
|
717
741
|
})
|
|
718
742
|
.slice(0, 16)
|
package/server/sat-i18n.mjs
CHANGED
|
@@ -68,6 +68,13 @@ const STR = {
|
|
|
68
68
|
already_replace: "Replace — new login, old device is revoked",
|
|
69
69
|
already_kept: "Left as is. Pulse sent. Same device in the cabinet.",
|
|
70
70
|
already_no_key: "Daemon is on, but this user cannot read the key. Try: sudo orb44 status",
|
|
71
|
+
update_same: "Already {version}.",
|
|
72
|
+
update_wrote: "Updated to {version} → {path}",
|
|
73
|
+
update_need_root: "Cannot write the install tree. Run: sudo orb44 update",
|
|
74
|
+
update_no_tree: "No satellite install to overwrite. After install: sudo orb44 update. Until then pin npx.",
|
|
75
|
+
update_fail: "Update failed{err}",
|
|
76
|
+
update_npx: "Do not run bare npx @orb44/cli — pin: npx @orb44/cli@{version}",
|
|
77
|
+
update_restarted: "Restarted orb44-satellite.",
|
|
71
78
|
advice_title: "What to do on this machine",
|
|
72
79
|
advice_sub: "The cabinet will not run this — a leaked key must not become a remote shell.",
|
|
73
80
|
preview_host: "host {h}",
|
|
@@ -109,6 +116,7 @@ const STR = {
|
|
|
109
116
|
orb44 uninstall [--purge]
|
|
110
117
|
orb44 lang [en|ru|ko|es]
|
|
111
118
|
orb44 status
|
|
119
|
+
orb44 update
|
|
112
120
|
orb44 logout
|
|
113
121
|
|
|
114
122
|
Login asks language (saved) then daemon (default no).
|
|
@@ -164,6 +172,13 @@ Outgoing pulse. Cabinet does not execute commands; it replies with advice.`,
|
|
|
164
172
|
already_replace: "Заменить — новый login, старое устройство отзовётся",
|
|
165
173
|
already_kept: "Оставили как есть. Пульс ушёл. В кабинете то же устройство.",
|
|
166
174
|
already_no_key: "Демон работает, но этот пользователь не читает ключ. Попробуйте: sudo orb44 status",
|
|
175
|
+
update_same: "Уже {version}.",
|
|
176
|
+
update_wrote: "Обновлено до {version} → {path}",
|
|
177
|
+
update_need_root: "Нельзя записать дерево установки. Запустите: sudo orb44 update",
|
|
178
|
+
update_no_tree: "Нет дерева сателлита для перезаписи. После install: sudo orb44 update. До этого — pin в npx.",
|
|
179
|
+
update_fail: "Обновление не вышло{err}",
|
|
180
|
+
update_npx: "Не вызывайте голый npx @orb44/cli — pin: npx @orb44/cli@{version}",
|
|
181
|
+
update_restarted: "Демон orb44-satellite перезапущен.",
|
|
167
182
|
advice_title: "Что сделать на этой машине",
|
|
168
183
|
advice_sub: "Кабинет это не выполнит — иначе утечка ключа была бы удалённым шеллом.",
|
|
169
184
|
preview_host: "хост {h}",
|
|
@@ -205,6 +220,7 @@ Outgoing pulse. Cabinet does not execute commands; it replies with advice.`,
|
|
|
205
220
|
orb44 uninstall [--purge]
|
|
206
221
|
orb44 lang [en|ru|ko|es]
|
|
207
222
|
orb44 status
|
|
223
|
+
orb44 update
|
|
208
224
|
orb44 logout
|
|
209
225
|
|
|
210
226
|
После login спрашивает язык (запоминает), затем демон — по умолчанию нет.
|
|
@@ -260,6 +276,13 @@ Outgoing pulse. Cabinet does not execute commands; it replies with advice.`,
|
|
|
260
276
|
already_replace: "교체 — 새 login, 이전 장치는 취소됨",
|
|
261
277
|
already_kept: "그대로 두었습니다. 펄스를 보냈습니다. 콘솔의 장치는 같습니다.",
|
|
262
278
|
already_no_key: "데몬은 켜져 있지만 이 사용자는 키를 읽지 못합니다. sudo orb44 status",
|
|
279
|
+
update_same: "이미 {version}.",
|
|
280
|
+
update_wrote: "{version}(으)로 갱신 → {path}",
|
|
281
|
+
update_need_root: "설치 트리를 쓸 수 없습니다. sudo orb44 update",
|
|
282
|
+
update_no_tree: "덮어쓸 위성 설치가 없습니다. install 후 sudo orb44 update. 그 전에는 npx를 pin 하세요.",
|
|
283
|
+
update_fail: "업데이트 실패{err}",
|
|
284
|
+
update_npx: "버전 없는 npx @orb44/cli 는 쓰지 마세요 — pin: npx @orb44/cli@{version}",
|
|
285
|
+
update_restarted: "orb44-satellite 를 재시작했습니다.",
|
|
263
286
|
advice_title: "이 기기에서 할 일",
|
|
264
287
|
advice_sub: "콘솔은 실행하지 않습니다. 키 유출이 원격 셸이 되면 안 됩니다.",
|
|
265
288
|
preview_host: "호스트 {h}",
|
|
@@ -301,6 +324,7 @@ Outgoing pulse. Cabinet does not execute commands; it replies with advice.`,
|
|
|
301
324
|
orb44 uninstall [--purge]
|
|
302
325
|
orb44 lang [en|ru|ko|es]
|
|
303
326
|
orb44 status
|
|
327
|
+
orb44 update
|
|
304
328
|
orb44 logout
|
|
305
329
|
|
|
306
330
|
login에서 언어를 묻고 저장한 뒤, 데몬은 기본값 아니오입니다.
|
|
@@ -356,6 +380,13 @@ login에서 언어를 묻고 저장한 뒤, 데몬은 기본값 아니오입니
|
|
|
356
380
|
already_replace: "Sustituir — login nuevo, se revoca el dispositivo anterior",
|
|
357
381
|
already_kept: "Se dejó igual. Pulso enviado. El mismo dispositivo en el gabinete.",
|
|
358
382
|
already_no_key: "El demonio está activo, pero este usuario no lee la clave. Pruebe: sudo orb44 status",
|
|
383
|
+
update_same: "Ya está {version}.",
|
|
384
|
+
update_wrote: "Actualizado a {version} → {path}",
|
|
385
|
+
update_need_root: "No se puede escribir el árbol. Ejecute: sudo orb44 update",
|
|
386
|
+
update_no_tree: "No hay instalación del satélite que sobrescribir. Tras install: sudo orb44 update. Mientras tanto, fije npx.",
|
|
387
|
+
update_fail: "La actualización falló{err}",
|
|
388
|
+
update_npx: "No use npx @orb44/cli sin versión — fije: npx @orb44/cli@{version}",
|
|
389
|
+
update_restarted: "orb44-satellite reiniciado.",
|
|
359
390
|
advice_title: "Qué hacer en esta máquina",
|
|
360
391
|
advice_sub: "El gabinete no lo ejecutará: una clave filtrada no debe ser un shell remoto.",
|
|
361
392
|
preview_host: "host {h}",
|
|
@@ -397,6 +428,7 @@ login에서 언어를 묻고 저장한 뒤, 데몬은 기본값 아니오입니
|
|
|
397
428
|
orb44 uninstall [--purge]
|
|
398
429
|
orb44 lang [en|ru|ko|es]
|
|
399
430
|
orb44 status
|
|
431
|
+
orb44 update
|
|
400
432
|
orb44 logout
|
|
401
433
|
|
|
402
434
|
Login pregunta idioma (lo guarda) y luego demonio (por defecto no).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
export const CLI_PACKAGE = "@orb44/cli";
|
|
6
|
+
export const SAT_TREE_FILES = [
|
|
7
|
+
"bin/orb44.mjs",
|
|
8
|
+
"server/pulse.mjs",
|
|
9
|
+
"server/sat-i18n.mjs",
|
|
10
|
+
"server/cli-menu.mjs",
|
|
11
|
+
"server/sat-local.mjs",
|
|
12
|
+
"server/sat-update.mjs",
|
|
13
|
+
"package.json",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const REGISTRY_LATEST = "https://registry.npmjs.org/@orb44%2fcli/latest";
|
|
17
|
+
|
|
18
|
+
export function cmpVer(a, b) {
|
|
19
|
+
const pa = String(a || "0").split(".").map((n) => Number(n) || 0);
|
|
20
|
+
const pb = String(b || "0").split(".").map((n) => Number(n) || 0);
|
|
21
|
+
const n = Math.max(pa.length, pb.length);
|
|
22
|
+
for (let i = 0; i < n; i++) {
|
|
23
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
24
|
+
if (d) return d < 0 ? -1 : 1;
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function looksLikeSat(root) {
|
|
30
|
+
const r = path.resolve(root);
|
|
31
|
+
if (!fs.existsSync(path.join(r, "bin", "orb44.mjs"))) return false;
|
|
32
|
+
if (!fs.existsSync(path.join(r, "server", "pulse.mjs"))) return false;
|
|
33
|
+
if (fs.existsSync(path.join(r, "server", "index.mjs"))) return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readCliVersion(scriptFile) {
|
|
38
|
+
const binDir = path.dirname(path.resolve(scriptFile));
|
|
39
|
+
const root = path.join(binDir, "..");
|
|
40
|
+
const candidates = [
|
|
41
|
+
path.join(root, "package.json"),
|
|
42
|
+
path.join(root, "packages", "orb44", "package.json"),
|
|
43
|
+
];
|
|
44
|
+
for (const p of candidates) {
|
|
45
|
+
try {
|
|
46
|
+
const j = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
47
|
+
if (j.name === CLI_PACKAGE && j.version) return String(j.version);
|
|
48
|
+
} catch {
|
|
49
|
+
/* next */
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return "0.0.0";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function pickSatRoots(scriptFile, { systemLib = "/usr/lib/orb44-sat" } = {}) {
|
|
56
|
+
const here = path.resolve(path.dirname(scriptFile), "..");
|
|
57
|
+
const out = [];
|
|
58
|
+
if (looksLikeSat(here)) out.push(here);
|
|
59
|
+
const lib = systemLib ? path.resolve(systemLib) : "";
|
|
60
|
+
if (lib && looksLikeSat(lib) && !out.includes(lib)) out.push(lib);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function applyUpdateTree(srcPackageDir, destRoot) {
|
|
65
|
+
const src = path.resolve(srcPackageDir);
|
|
66
|
+
const dest = path.resolve(destRoot);
|
|
67
|
+
for (const rel of SAT_TREE_FILES) {
|
|
68
|
+
const from = path.join(src, rel);
|
|
69
|
+
if (!fs.existsSync(from)) {
|
|
70
|
+
if (rel === "package.json") continue;
|
|
71
|
+
throw new Error(`missing ${rel}`);
|
|
72
|
+
}
|
|
73
|
+
const to = path.join(dest, rel);
|
|
74
|
+
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
75
|
+
fs.copyFileSync(from, to);
|
|
76
|
+
fs.chmodSync(to, rel.endsWith("orb44.mjs") ? 0o755 : 0o644);
|
|
77
|
+
}
|
|
78
|
+
return dest;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function fetchLatestMeta(fetchFn = fetch) {
|
|
82
|
+
const r = await fetchFn(REGISTRY_LATEST, { headers: { Accept: "application/json" } });
|
|
83
|
+
if (!r?.ok) throw new Error(`registry ${r?.status || "fail"}`);
|
|
84
|
+
const j = await r.json();
|
|
85
|
+
const version = j?.version;
|
|
86
|
+
const tarball = j?.dist?.tarball;
|
|
87
|
+
if (!version || !tarball) throw new Error("registry meta");
|
|
88
|
+
return { version: String(version), tarball: String(tarball) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function unpackTarball(url, tmp, fetchFn = fetch) {
|
|
92
|
+
const r = await fetchFn(url);
|
|
93
|
+
if (!r?.ok) throw new Error(`tarball ${r?.status || "fail"}`);
|
|
94
|
+
const buf = Buffer.from(await r.arrayBuffer());
|
|
95
|
+
const tgz = path.join(tmp, "pkg.tgz");
|
|
96
|
+
fs.writeFileSync(tgz, buf);
|
|
97
|
+
const unpack = path.join(tmp, "unpack");
|
|
98
|
+
fs.mkdirSync(unpack, { recursive: true });
|
|
99
|
+
const tar = spawnSync("tar", ["-xzf", tgz, "-C", unpack], { encoding: "utf8" });
|
|
100
|
+
if (tar.status !== 0) throw new Error((tar.stderr || tar.stdout || "tar").trim().slice(0, 200));
|
|
101
|
+
const pkg = path.join(unpack, "package");
|
|
102
|
+
if (!fs.existsSync(path.join(pkg, "bin", "orb44.mjs"))) throw new Error("bad tarball");
|
|
103
|
+
return pkg;
|
|
104
|
+
}
|