windscout 0.5.6 → 0.5.8
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/cli.js +26 -16
- package/dist/config.js +1 -0
- package/dist/tui/picker.js +22 -14
- package/dist/tui/progress.js +4 -2
- package/dist/update.js +42 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ WindScout drives the official `windscribe-cli`: it connects to each country × p
|
|
|
20
20
|
npm install -g windscout # or run once with: npx windscout
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
On startup, WindScout checks npm for a newer version.
|
|
23
|
+
On startup, WindScout checks npm for a newer version. Above the repository link at the bottom of the screen, it shows the installed version in grey (`windscout 0.5.6 · latest` once npm confirms nothing newer is out), or, when a newer one is out, an update notice in yellow. In the picker, press `u` to update and restart on the new version. The update runs `npm install -g windscout@latest`, with `sudo` when your global npm folder belongs to root (sudo then asks for your password). The update command is also printed after the final report.
|
|
24
24
|
|
|
25
25
|
## Usage
|
|
26
26
|
|
|
@@ -52,6 +52,8 @@ Run `windscout` with no arguments to open the interactive picker:
|
|
|
52
52
|
| `1`–`6` | toggle a protocol (`1`–`5` on Linux, which has no IKEv2) |
|
|
53
53
|
| `s` | toggle the speed test (off = connect-only mode) |
|
|
54
54
|
| `f` | toggle "block internet while scanning" (firewall) |
|
|
55
|
+
| `r` | toggle random order: scan the selected countries shuffled, a new order each run |
|
|
56
|
+
| `u` | update to the newer version and restart (only when the picker shows one is out) |
|
|
55
57
|
| `enter` | start scanning |
|
|
56
58
|
| `q` | quit (during a scan: stop and show partial results; press again to force quit) |
|
|
57
59
|
|
|
@@ -79,7 +81,8 @@ windscout -t 40 -a # longer connect timeout, also list
|
|
|
79
81
|
| `-a, --all` | include failed attempts in the final table | off |
|
|
80
82
|
| `-n, --no-speed` | connect-only mode: skip the download test, rank by latency | picker: saved, or connect-only on first run; with `-c`: speed test on |
|
|
81
83
|
| `-f, --firewall` | block internet while scanning, except through the VPN | picker: saved, or off; with `-c`: off |
|
|
82
|
-
| `-
|
|
84
|
+
| `-r, --random` | scan the countries in a random order | picker: saved, or off; with `-c`: off |
|
|
85
|
+
| `-v, --version` | print the installed version; if a newer one is on npm, show the update command (with `sudo` when needed) and ask (y/n) to run it | |
|
|
83
86
|
|
|
84
87
|
## Connect-only mode
|
|
85
88
|
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { command, report, target } from "./tui/report.js";
|
|
|
13
13
|
import { leaveScreen, out } from "./tui/terminal.js";
|
|
14
14
|
import { cyan, green, yellow } from "./tui/text.js";
|
|
15
15
|
import { now, sleep } from "./time.js";
|
|
16
|
-
import { checkUpdate,
|
|
16
|
+
import { canUpdate, checkUpdate, runUpdate, updateNotice, versionLines } from "./update.js";
|
|
17
17
|
import { blockInternet, CLI, connectState, isFirewallOn, PROTOCOLS, restoreFirewall, waitForLogin, windscribe } from "./windscribe.js";
|
|
18
18
|
const HELP = `Usage: windscout [options]
|
|
19
19
|
|
|
@@ -27,6 +27,7 @@ Options:
|
|
|
27
27
|
-a, --all also show failed attempts in the final table
|
|
28
28
|
-n, --no-speed connect-only: skip the download test, rank by latency (faster scan)
|
|
29
29
|
-f, --firewall block internet while scanning, except through the VPN (turned back off afterwards)
|
|
30
|
+
-r, --random scan the countries in a random order
|
|
30
31
|
-v, --version show the installed version (and offer to update if a newer one is out)
|
|
31
32
|
-h, --help show this help
|
|
32
33
|
|
|
@@ -61,6 +62,17 @@ async function offerConnect(choices, timeout) {
|
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
}
|
|
65
|
+
/** u in the picker: update, then run the new version in this one's place, with the same arguments */
|
|
66
|
+
function updateAndRestart() {
|
|
67
|
+
const status = runUpdate();
|
|
68
|
+
if (status) {
|
|
69
|
+
console.error(yellow(`update failed (exit ${status})`));
|
|
70
|
+
process.exitCode = status;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
console.log(green("updated · restarting windscout"));
|
|
74
|
+
process.exitCode = spawnSync(process.execPath, [...process.execArgv, ...process.argv.slice(1)], { stdio: "inherit" }).status ?? 1;
|
|
75
|
+
}
|
|
64
76
|
async function main() {
|
|
65
77
|
const { values: a } = parseArgs({
|
|
66
78
|
options: {
|
|
@@ -72,25 +84,17 @@ async function main() {
|
|
|
72
84
|
version: { type: "boolean", short: "v", default: false },
|
|
73
85
|
"no-speed": { type: "boolean", short: "n", default: false },
|
|
74
86
|
firewall: { type: "boolean", short: "f", default: false },
|
|
87
|
+
random: { type: "boolean", short: "r", default: false },
|
|
75
88
|
},
|
|
76
89
|
});
|
|
77
90
|
if (a.help)
|
|
78
91
|
return console.log(HELP);
|
|
79
92
|
checkUpdate(); // runs while the app starts and the user picks, before the VPN starts switching
|
|
80
93
|
if (a.version) {
|
|
81
|
-
console.log(`windscout ${VERSION}`);
|
|
82
94
|
await checkUpdate();
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
console.error(yellow(notice));
|
|
87
|
-
if (process.stdin.isTTY && out.isTTY && !viaNpx() && /^y(es)?$/i.test(await ask("update now? (y/n) "))) {
|
|
88
|
-
console.log(cyan(UPDATE_COMMAND));
|
|
89
|
-
// shell: npm is npm.cmd on Windows
|
|
90
|
-
const { status } = spawnSync(UPDATE_COMMAND, { shell: true, stdio: "inherit" });
|
|
91
|
-
if (status)
|
|
92
|
-
process.exitCode = status;
|
|
93
|
-
}
|
|
95
|
+
console.log(versionLines().join("\n"));
|
|
96
|
+
if (canUpdate() && process.stdin.isTTY && out.isTTY && /^y(es)?$/i.test(await ask("update now? (y/n) ")))
|
|
97
|
+
process.exitCode = runUpdate();
|
|
94
98
|
return;
|
|
95
99
|
}
|
|
96
100
|
let timeout = a.timeout ? Number(a.timeout) : undefined;
|
|
@@ -102,6 +106,7 @@ async function main() {
|
|
|
102
106
|
let protocols = a.protocols ? list(a.protocols) : undefined;
|
|
103
107
|
let speedTest = !a["no-speed"];
|
|
104
108
|
let firewall = a.firewall;
|
|
109
|
+
let random = a.random;
|
|
105
110
|
if (!countries) {
|
|
106
111
|
if (!tty)
|
|
107
112
|
throw new Error("no terminal for the picker: pass countries with -c");
|
|
@@ -113,17 +118,23 @@ async function main() {
|
|
|
113
118
|
speedTest: speedTest && (saved?.speed ?? false), // first run: connect-only
|
|
114
119
|
// already on in the app: show it on, since the scan won't turn it off
|
|
115
120
|
firewall: firewall || isFirewallOn(status) || (saved?.firewall ?? false),
|
|
121
|
+
random: random || (saved?.random ?? false),
|
|
116
122
|
});
|
|
117
123
|
if (!choice)
|
|
118
124
|
return;
|
|
125
|
+
if (choice === "update")
|
|
126
|
+
return updateAndRestart();
|
|
119
127
|
countries = choice.countries;
|
|
120
128
|
protocols ??= choice.protocols;
|
|
121
129
|
speedTest = choice.speed;
|
|
122
130
|
firewall = choice.firewall;
|
|
131
|
+
random = choice.random;
|
|
123
132
|
timeout = choice.timeout;
|
|
124
133
|
}
|
|
125
134
|
protocols ??= PROTOCOLS;
|
|
126
135
|
timeout ??= DEFAULT_TIMEOUT;
|
|
136
|
+
if (random)
|
|
137
|
+
countries = countries.map((c) => [Math.random(), c]).sort(([x], [y]) => x - y).map(([, c]) => c);
|
|
127
138
|
const scan = new Scan(countries, protocols, { timeout, speedTest });
|
|
128
139
|
const blocking = firewall && (await blockInternet());
|
|
129
140
|
const view = tty ? showProgress(scan, blocking) : logProgress(scan);
|
|
@@ -134,9 +145,8 @@ async function main() {
|
|
|
134
145
|
console.log(yellow("interrupted, showing partial results"));
|
|
135
146
|
const choices = report(scan.results, protocols, { all: a.all, speedTest });
|
|
136
147
|
await checkUpdate();
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
console.error("\n" + yellow(notice));
|
|
148
|
+
if (updateNotice())
|
|
149
|
+
console.error("\n" + versionLines().join("\n"));
|
|
140
150
|
if (tty && choices.length)
|
|
141
151
|
await offerConnect(choices, timeout);
|
|
142
152
|
}
|
package/dist/config.js
CHANGED
|
@@ -15,6 +15,7 @@ export function loadConfig() {
|
|
|
15
15
|
protocols: (c.protocols ?? []).filter((x) => PROTOCOLS.includes(x)),
|
|
16
16
|
speed: c.speed !== false,
|
|
17
17
|
firewall: c.firewall === true,
|
|
18
|
+
random: c.random === true,
|
|
18
19
|
timeout: c.timeout >= MIN_TIMEOUT ? c.timeout : DEFAULT_TIMEOUT,
|
|
19
20
|
};
|
|
20
21
|
}
|
package/dist/tui/picker.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { MIN_TIMEOUT, saveConfig } from "../config.js";
|
|
2
2
|
import { attemptSeconds, estimate } from "../scan.js";
|
|
3
3
|
import { duration } from "../time.js";
|
|
4
|
-
import { checkUpdate,
|
|
4
|
+
import { canUpdate, checkUpdate, versionLines } from "../update.js";
|
|
5
5
|
import { COUNTRIES, PROTOCOLS } from "../windscribe.js";
|
|
6
6
|
import { countryName, flag } from "./country.js";
|
|
7
7
|
import { draw, enterScreen, input, leaveScreen, out, repoLink } from "./terminal.js";
|
|
8
8
|
import { bold, cyan, dim, green, under, vw, yellow } from "./text.js";
|
|
9
9
|
import { box, centerIn, grid } from "./widgets.js";
|
|
10
10
|
const flip = (set, value) => (set.has(value) ? set.delete(value) : set.add(value));
|
|
11
|
-
/** Resolves to the choice (also saved for next time),
|
|
12
|
-
export function pick(saved, { timeout, speedTest, firewall }) {
|
|
11
|
+
/** Resolves to the choice (also saved for next time), null when the user quits, or "update" for u */
|
|
12
|
+
export function pick(saved, { timeout, speedTest, firewall, random }) {
|
|
13
13
|
const selected = new Set(saved?.countries ?? []);
|
|
14
14
|
const protos = new Set(saved?.protocols.length ? saved.protocols : PROTOCOLS);
|
|
15
15
|
let cursor = 0, top = 0, message = "", query = "", searching = false;
|
|
@@ -23,9 +23,9 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
23
23
|
return new Promise((resolve) => {
|
|
24
24
|
const render = () => {
|
|
25
25
|
const rows = out.rows || 24;
|
|
26
|
-
const
|
|
26
|
+
const version = versionLines(true);
|
|
27
27
|
const matches = visible();
|
|
28
|
-
const height = Math.max(3, rows -
|
|
28
|
+
const height = Math.max(3, rows - 22 - version.length);
|
|
29
29
|
cursor = Math.max(0, Math.min(cursor, matches.length - 1));
|
|
30
30
|
if (cursor < top)
|
|
31
31
|
top = cursor;
|
|
@@ -40,6 +40,9 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
40
40
|
const fwLine = `${dim(under("f"))} ${firewall ? green("● " + fwLabel) : dim("○ " + fwLabel)} ${dim(firewall
|
|
41
41
|
? "only VPN traffic gets through"
|
|
42
42
|
: "off: apps may leak in between")}`;
|
|
43
|
+
const randomLine = `${dim(under("r"))} ${random ? green("● random order") : dim("○ random order")} ${dim(random
|
|
44
|
+
? "scans the countries shuffled, a new order each run"
|
|
45
|
+
: "off: scans in list order")}`;
|
|
43
46
|
timeout = Number(seconds);
|
|
44
47
|
const short = timeout < MIN_TIMEOUT;
|
|
45
48
|
const timeoutLine = `${dim(under("t"))} timeout ${editing ? bold(seconds) + cyan("▏") : (short ? yellow : bold)(seconds || "?")} s ${dim(`per connection · at least ${MIN_TIMEOUT}`)}`;
|
|
@@ -63,9 +66,9 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
63
66
|
["quit", under("Q") + "uit"],
|
|
64
67
|
];
|
|
65
68
|
const buttonLine = buttons.map(([id, label]) => (id === "start" ? bold(cyan(`[ ${label} ]`)) : dim(`[ ${label} ]`))).join(" ");
|
|
66
|
-
const table = grid([{ title: "" }, { title: "Code" }, { title: "Country" }], body, { caption: search, captionRight: dim(range), minWidth: Math.max(vw(protoLine), vw(speedLine), vw(fwLine), vw(timeoutLine)) + 4 });
|
|
69
|
+
const table = grid([{ title: "" }, { title: "Code" }, { title: "Country" }], body, { caption: search, captionRight: dim(range), minWidth: Math.max(vw(protoLine), vw(speedLine), vw(fwLine), vw(randomLine), vw(timeoutLine)) + 4 });
|
|
67
70
|
const width = vw(table[0]);
|
|
68
|
-
const head = box([protoLine, speedLine, fwLine, timeoutLine], {
|
|
71
|
+
const head = box([protoLine, speedLine, fwLine, randomLine, timeoutLine], {
|
|
69
72
|
title: bold(cyan("WindScout")),
|
|
70
73
|
right: `${bold(String(selected.size))}${dim(`/${COUNTRIES.length} selected`)}`,
|
|
71
74
|
width: width - 4,
|
|
@@ -85,13 +88,11 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
85
88
|
: searching
|
|
86
89
|
? "type to filter · ↑↓ move · enter/esc done"
|
|
87
90
|
: `↑↓ move · space select · a/n ${query ? "all/none shown" : "all/none"} · / search`), width),
|
|
88
|
-
centerIn(dim(searching || editing ? "" : `1-${PROTOCOLS.length} protocols · s speed test · f firewall · t timeout · enter start · ${query ? "esc clear · " : ""}q quit`), width),
|
|
91
|
+
centerIn(dim(searching || editing ? "" : `1-${PROTOCOLS.length} protocols · s speed test · f firewall · r random · t timeout · enter start · ${query ? "esc clear · " : ""}q quit`), width),
|
|
89
92
|
];
|
|
90
93
|
if (message)
|
|
91
94
|
lines.push(centerIn(yellow(message), width));
|
|
92
|
-
lines.push("", centerIn(dim(repoLink), width));
|
|
93
|
-
if (notice)
|
|
94
|
-
lines.push(centerIn(yellow(notice), width));
|
|
95
|
+
lines.push("", ...version.map((l) => centerIn(l, width)), centerIn(dim(repoLink), width));
|
|
95
96
|
const at = draw(lines);
|
|
96
97
|
// click map, in screen coordinates
|
|
97
98
|
let px = at.left + 2; // "│ " before the protocol line
|
|
@@ -109,20 +110,20 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
109
110
|
return range;
|
|
110
111
|
});
|
|
111
112
|
hit = {
|
|
112
|
-
protoRow: at.top + 1, protoHits, speedRow: at.top + 2, firewallRow: at.top + 3,
|
|
113
|
+
protoRow: at.top + 1, protoHits, speedRow: at.top + 2, firewallRow: at.top + 3, randomRow: at.top + 4, timeoutRow: at.top + 5,
|
|
113
114
|
searchRow: at.top + head.length + 1,
|
|
114
115
|
firstRow: at.top + head.length + 5, shown: shown.length,
|
|
115
116
|
buttonRow: at.top + head.length + table.length + 1, buttonHits,
|
|
116
117
|
};
|
|
117
118
|
};
|
|
118
119
|
let closed = false;
|
|
119
|
-
checkUpdate().then(() =>
|
|
120
|
+
checkUpdate().then(() => !closed && render()); // the check usually lands after the first draw
|
|
120
121
|
const finish = (value) => {
|
|
121
122
|
closed = true;
|
|
122
123
|
out.off("resize", render);
|
|
123
124
|
input.removeAllListeners("keypress");
|
|
124
125
|
input.removeAllListeners("mouse");
|
|
125
|
-
if (!value)
|
|
126
|
+
if (!value || value === "update")
|
|
126
127
|
leaveScreen();
|
|
127
128
|
resolve(value);
|
|
128
129
|
};
|
|
@@ -141,6 +142,7 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
141
142
|
protocols: PROTOCOLS.filter((p) => protos.has(p)),
|
|
142
143
|
speed: speedTest,
|
|
143
144
|
firewall,
|
|
145
|
+
random,
|
|
144
146
|
timeout,
|
|
145
147
|
};
|
|
146
148
|
saveConfig(choice);
|
|
@@ -180,6 +182,8 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
180
182
|
speedTest = !speedTest;
|
|
181
183
|
else if (y === hit.firewallRow)
|
|
182
184
|
firewall = !firewall;
|
|
185
|
+
else if (y === hit.randomRow)
|
|
186
|
+
random = !random;
|
|
183
187
|
else if (y === hit.buttonRow) {
|
|
184
188
|
const id = hit.buttonHits.find(([, a, b]) => x >= a && x < b)?.[0];
|
|
185
189
|
if (id === "start" && start())
|
|
@@ -255,8 +259,12 @@ export function pick(saved, { timeout, speedTest, firewall }) {
|
|
|
255
259
|
speedTest = !speedTest;
|
|
256
260
|
else if (k === "f")
|
|
257
261
|
firewall = !firewall;
|
|
262
|
+
else if (k === "r")
|
|
263
|
+
random = !random;
|
|
258
264
|
else if (k === "t")
|
|
259
265
|
editing = true, seconds = "";
|
|
266
|
+
else if (k === "u" && canUpdate())
|
|
267
|
+
return finish("update");
|
|
260
268
|
else if (/^[1-9]$/.test(str ?? "") && PROTOCOLS[Number(str) - 1])
|
|
261
269
|
flip(protos, PROTOCOLS[Number(str) - 1]);
|
|
262
270
|
else if (k === "escape" && query)
|
package/dist/tui/progress.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { sortOk } from "../probe.js";
|
|
2
2
|
import { attemptSeconds } from "../scan.js";
|
|
3
3
|
import { duration, now } from "../time.js";
|
|
4
|
+
import { versionLines } from "../update.js";
|
|
4
5
|
import { CLI, restoreFirewall } from "../windscribe.js";
|
|
5
6
|
import { countryName, flag } from "./country.js";
|
|
6
7
|
import { matrix, resultTable } from "./tables.js";
|
|
@@ -34,7 +35,8 @@ export function showProgress(scan, blocking) {
|
|
|
34
35
|
].join(dim(" │ "));
|
|
35
36
|
const minW = Math.max(64, vw(stats) + 6); // panel and table share one width
|
|
36
37
|
// results table gets whatever height the status panel and footer leave
|
|
37
|
-
const
|
|
38
|
+
const version = versionLines();
|
|
39
|
+
const room = Math.max(1, rows - 22 - version.length);
|
|
38
40
|
const table = view === "availability"
|
|
39
41
|
? matrix(results, protocols, { minWidth: minW, limit: room })
|
|
40
42
|
: ok.length
|
|
@@ -73,7 +75,7 @@ export function showProgress(scan, blocking) {
|
|
|
73
75
|
txt = txt.slice(0, -8) + "…"; // ponytail: crude trim, fine for a status line
|
|
74
76
|
lines.push("", centerIn(txt, width));
|
|
75
77
|
}
|
|
76
|
-
lines.push("", centerIn(dim(`tab ${view === "ranking" ? "availability" : "ranking"} view · p ${paused ? "resume" : "pause"} · q stop · q twice force quit`), width), "", centerIn(dim(repoLink), width));
|
|
78
|
+
lines.push("", centerIn(dim(`tab ${view === "ranking" ? "availability" : "ranking"} view · p ${paused ? "resume" : "pause"} · q stop · q twice force quit`), width), "", ...version.map((l) => centerIn(l, width)), centerIn(dim(repoLink), width));
|
|
77
79
|
draw(lines);
|
|
78
80
|
};
|
|
79
81
|
enterScreen();
|
package/dist/update.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
// The installed version,
|
|
2
|
-
import {
|
|
1
|
+
// The installed version, whether npm has a newer one, and updating to it.
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { accessSync, constants, readFileSync } from "node:fs";
|
|
4
|
+
import { cyan, dim, under, yellow } from "./tui/text.js";
|
|
3
5
|
export const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
4
6
|
let latest = null; // newer version on npm, once known
|
|
7
|
+
let upToDate = false; // npm answered, and has nothing newer
|
|
5
8
|
let check;
|
|
6
9
|
// numeric x.y.z compare; parseInt drops a prerelease suffix ("0-beta" → 0)
|
|
7
10
|
export function newer(a, b) {
|
|
@@ -15,17 +18,48 @@ export function newer(a, b) {
|
|
|
15
18
|
export function checkUpdate() {
|
|
16
19
|
check ??= fetch("https://registry.npmjs.org/windscout/latest", { signal: AbortSignal.timeout(3000) })
|
|
17
20
|
.then((r) => r.json())
|
|
18
|
-
.then(({ version }) => {
|
|
19
|
-
|
|
21
|
+
.then(({ version }) => {
|
|
22
|
+
if (version && newer(version, VERSION))
|
|
23
|
+
latest = version;
|
|
24
|
+
else
|
|
25
|
+
upToDate = Boolean(version);
|
|
26
|
+
})
|
|
20
27
|
.catch(() => { });
|
|
21
28
|
return check;
|
|
22
29
|
}
|
|
23
30
|
// run through npx: there's nothing installed to update, the next npx windscout@latest fetches it
|
|
24
31
|
export const viaNpx = () => process.env.npm_command === "exec";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
// npm replaces the folder this package sits in; a root-owned global folder (system Node) needs sudo for that
|
|
33
|
+
const sudo = (() => {
|
|
34
|
+
try {
|
|
35
|
+
accessSync(new URL("../..", import.meta.url), constants.W_OK);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
})();
|
|
42
|
+
const INSTALL = "npm install -g windscout@latest";
|
|
43
|
+
export const UPDATE_COMMAND = (sudo ? "sudo " : "") + INSTALL;
|
|
44
|
+
// npm is Node too: through a slow tunnel its downloads time out like fetch did (see measure.ts) without these
|
|
45
|
+
const NODE_FIX = "--no-network-family-autoselection --dns-result-order=ipv4first";
|
|
46
|
+
/** A newer version is out, and npm can install it (under npx there's nothing installed to update) */
|
|
47
|
+
export const canUpdate = () => latest !== null && !viaNpx();
|
|
48
|
+
/** Runs the update in this terminal (sudo asks for the password here); returns npm's exit status */
|
|
49
|
+
export function runUpdate() {
|
|
50
|
+
console.log(cyan(UPDATE_COMMAND));
|
|
51
|
+
const NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ""} ${NODE_FIX}`.trim();
|
|
52
|
+
// sudo clears the environment, so env sets NODE_OPTIONS on its side; shell: npm is npm.cmd on Windows
|
|
53
|
+
return spawnSync(sudo ? `sudo env NODE_OPTIONS="${NODE_FIX}" ${INSTALL}` : INSTALL, { shell: true, stdio: "inherit", env: { ...process.env, NODE_OPTIONS } }).status ?? 1;
|
|
54
|
+
}
|
|
55
|
+
/** "update available: …" and how to get it, once checkUpdate() found a newer version; onKey: this screen updates on u */
|
|
56
|
+
export function updateNotice(onKey = false) {
|
|
28
57
|
if (!latest)
|
|
29
58
|
return null;
|
|
30
|
-
return `update available: ${VERSION} → ${latest}
|
|
59
|
+
return [`update available: ${VERSION} → ${latest}`,
|
|
60
|
+
viaNpx() ? "quit, then run: npx windscout@latest"
|
|
61
|
+
: onKey ? `press ${under("u")} to update and restart · runs ${UPDATE_COMMAND}`
|
|
62
|
+
: `run: ${UPDATE_COMMAND}, then restart windscout`];
|
|
31
63
|
}
|
|
64
|
+
/** The version line(s) above the repo link: the update notice in yellow, or the installed version in grey */
|
|
65
|
+
export const versionLines = (onKey = false) => updateNotice(onKey)?.map(yellow) ?? [dim(`windscout ${VERSION}${upToDate ? " · latest" : ""}`)];
|