@vividengine/fastio-cli 0.2.85 → 0.2.87
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/install.js +158 -15
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const crypto = require("crypto");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const https = require("https");
|
|
7
|
-
const http = require("http");
|
|
8
7
|
const path = require("path");
|
|
9
8
|
const os = require("os");
|
|
9
|
+
const { URL } = require("url");
|
|
10
10
|
|
|
11
11
|
const REPO = "MediaFire/fastio_cli";
|
|
12
12
|
const VERSION = require("./package.json").version;
|
|
13
13
|
|
|
14
|
+
const MAX_REDIRECTS = 10;
|
|
15
|
+
// Abort a request that goes this long without receiving data.
|
|
16
|
+
const REQUEST_TIMEOUT_MS = 30 * 1000;
|
|
17
|
+
// Upper bound on the whole install, across every download. Generous so slow
|
|
18
|
+
// links still finish; stalled connections are caught by REQUEST_TIMEOUT_MS.
|
|
19
|
+
const OVERALL_TIMEOUT_MS = 20 * 60 * 1000;
|
|
20
|
+
|
|
14
21
|
const PLATFORM_MAP = {
|
|
15
22
|
"darwin-arm64": "fastio-darwin-arm64",
|
|
16
23
|
"darwin-x64": "fastio-darwin-x64",
|
|
@@ -46,24 +53,142 @@ function getBinaryPath() {
|
|
|
46
53
|
return path.join(dir, isWindows ? "fastio.exe" : "fastio");
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Parse `sha256sum` output (`<64 hex> <name>`, or `<64 hex> *<name>` in
|
|
58
|
+
* binary mode) into a Map of file name to lowercase hex digest. Blank lines
|
|
59
|
+
* are ignored; any other line that does not match the format, or a name
|
|
60
|
+
* listed twice with different digests, is an error.
|
|
61
|
+
*/
|
|
62
|
+
function parseChecksums(text) {
|
|
63
|
+
const sums = new Map();
|
|
64
|
+
const lines = String(text).split("\n");
|
|
65
|
+
for (let i = 0; i < lines.length; i++) {
|
|
66
|
+
const line = lines[i].replace(/\r$/, "");
|
|
67
|
+
if (line.trim() === "") {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const match = /^([0-9a-fA-F]{64}) [ *](.+)$/.exec(line);
|
|
71
|
+
if (!match) {
|
|
72
|
+
throw new Error(`Malformed SHA256SUMS line ${i + 1}`);
|
|
73
|
+
}
|
|
74
|
+
const hash = match[1].toLowerCase();
|
|
75
|
+
const name = match[2];
|
|
76
|
+
if (sums.has(name) && sums.get(name) !== hash) {
|
|
77
|
+
throw new Error(`Conflicting SHA256SUMS entries for ${name}`);
|
|
78
|
+
}
|
|
79
|
+
sums.set(name, hash);
|
|
80
|
+
}
|
|
81
|
+
return sums;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Return the expected digest for `name`, or throw if it is not listed. */
|
|
85
|
+
function expectedHashFor(sums, name) {
|
|
86
|
+
const hash = sums.get(name);
|
|
87
|
+
if (!hash) {
|
|
88
|
+
throw new Error(`No checksum for ${name} in SHA256SUMS`);
|
|
89
|
+
}
|
|
90
|
+
return hash;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function sha256Hex(data) {
|
|
94
|
+
return crypto.createHash("sha256").update(data).digest("hex");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Throw unless `data` hashes to the digest SHA256SUMS lists for `name`.
|
|
99
|
+
*/
|
|
100
|
+
function verifyChecksum(data, checksumsText, name) {
|
|
101
|
+
const expected = expectedHashFor(parseChecksums(checksumsText), name);
|
|
102
|
+
const actual = sha256Hex(data);
|
|
103
|
+
if (actual !== expected) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Checksum mismatch for ${name}: expected ${expected}, got ${actual}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolve a redirect `Location` against the URL that returned it. Only
|
|
112
|
+
* https targets are followed.
|
|
113
|
+
*/
|
|
114
|
+
function resolveRedirect(fromUrl, location) {
|
|
115
|
+
const next = new URL(location, fromUrl);
|
|
116
|
+
if (next.protocol !== "https:") {
|
|
117
|
+
throw new Error(`Refusing to follow redirect to non-https URL: ${next.href}`);
|
|
118
|
+
}
|
|
119
|
+
return next.href;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function download(url, redirectsLeft = MAX_REDIRECTS) {
|
|
50
123
|
return new Promise((resolve, reject) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
124
|
+
if (new URL(url).protocol !== "https:") {
|
|
125
|
+
reject(new Error(`Refusing to download from non-https URL: ${url}`));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const req = https.get(
|
|
129
|
+
url,
|
|
130
|
+
{ headers: { "User-Agent": "fastio-cli-npm" } },
|
|
131
|
+
(res) => {
|
|
54
132
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
55
|
-
|
|
133
|
+
res.resume();
|
|
134
|
+
if (redirectsLeft <= 0) {
|
|
135
|
+
reject(new Error("Download failed: too many redirects"));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let next;
|
|
139
|
+
try {
|
|
140
|
+
next = resolveRedirect(url, res.headers.location);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
reject(err);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
download(next, redirectsLeft - 1).then(resolve, reject);
|
|
146
|
+
return;
|
|
56
147
|
}
|
|
57
148
|
if (res.statusCode !== 200) {
|
|
58
|
-
|
|
149
|
+
res.resume();
|
|
150
|
+
reject(new Error(`Download failed: HTTP ${res.statusCode}`));
|
|
151
|
+
return;
|
|
59
152
|
}
|
|
60
153
|
const chunks = [];
|
|
61
154
|
res.on("data", (chunk) => chunks.push(chunk));
|
|
62
155
|
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
63
156
|
res.on("error", reject);
|
|
64
|
-
}
|
|
65
|
-
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
req.setTimeout(REQUEST_TIMEOUT_MS, () => {
|
|
160
|
+
req.destroy(new Error("Download failed: request timed out"));
|
|
161
|
+
});
|
|
162
|
+
req.on("error", reject);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function withTimeout(promise, ms, message) {
|
|
167
|
+
let timer;
|
|
168
|
+
const timeout = new Promise((_, reject) => {
|
|
169
|
+
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
66
170
|
});
|
|
171
|
+
return Promise.race([promise, timeout]).then(
|
|
172
|
+
(value) => {
|
|
173
|
+
clearTimeout(timer);
|
|
174
|
+
return value;
|
|
175
|
+
},
|
|
176
|
+
(err) => {
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function install(binaryName, binaryPath) {
|
|
184
|
+
const base = `https://github.com/${REPO}/releases/download/v${VERSION}`;
|
|
185
|
+
const checksums = (await download(`${base}/SHA256SUMS`)).toString("utf8");
|
|
186
|
+
// Fail on a missing or malformed entry before fetching the binary.
|
|
187
|
+
expectedHashFor(parseChecksums(checksums), binaryName);
|
|
188
|
+
const data = await download(`${base}/${binaryName}`);
|
|
189
|
+
verifyChecksum(data, checksums, binaryName);
|
|
190
|
+
fs.writeFileSync(binaryPath, data);
|
|
191
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
67
192
|
}
|
|
68
193
|
|
|
69
194
|
async function main() {
|
|
@@ -78,12 +203,19 @@ async function main() {
|
|
|
78
203
|
console.log(`Downloading fastio v${VERSION} for ${getPlatformKey()}...`);
|
|
79
204
|
|
|
80
205
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
206
|
+
await withTimeout(
|
|
207
|
+
install(binaryName, binaryPath),
|
|
208
|
+
OVERALL_TIMEOUT_MS,
|
|
209
|
+
"Download failed: timed out"
|
|
210
|
+
);
|
|
84
211
|
console.log(`Installed fastio to ${binaryPath}`);
|
|
85
212
|
} catch (err) {
|
|
86
|
-
|
|
213
|
+
try {
|
|
214
|
+
fs.unlinkSync(binaryPath);
|
|
215
|
+
} catch (_) {
|
|
216
|
+
// Nothing was written.
|
|
217
|
+
}
|
|
218
|
+
console.error(`Failed to install fastio: ${err.message}`);
|
|
87
219
|
console.error(`URL: ${url}`);
|
|
88
220
|
console.error("");
|
|
89
221
|
console.error("You can download manually from:");
|
|
@@ -92,4 +224,15 @@ async function main() {
|
|
|
92
224
|
}
|
|
93
225
|
}
|
|
94
226
|
|
|
95
|
-
main
|
|
227
|
+
if (require.main === module) {
|
|
228
|
+
main();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = {
|
|
232
|
+
MAX_REDIRECTS,
|
|
233
|
+
parseChecksums,
|
|
234
|
+
expectedHashFor,
|
|
235
|
+
sha256Hex,
|
|
236
|
+
verifyChecksum,
|
|
237
|
+
resolveRedirect,
|
|
238
|
+
};
|