hostedge 0.1.1 → 0.1.2
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/hostedge.js +83 -18
- package/package.json +1 -1
package/dist/hostedge.js
CHANGED
|
@@ -1320,19 +1320,21 @@ function exitCodeFor(status, code) {
|
|
|
1320
1320
|
return EXIT.failure;
|
|
1321
1321
|
}
|
|
1322
1322
|
var ApiError = class extends CliError {
|
|
1323
|
-
constructor(message, status, code) {
|
|
1323
|
+
constructor(message, status, code, retryAfter = null) {
|
|
1324
1324
|
super(message, exitCodeFor(status, code));
|
|
1325
1325
|
this.status = status;
|
|
1326
1326
|
this.code = code;
|
|
1327
|
+
this.retryAfter = retryAfter;
|
|
1327
1328
|
}
|
|
1328
1329
|
status;
|
|
1329
1330
|
code;
|
|
1331
|
+
retryAfter;
|
|
1330
1332
|
};
|
|
1331
1333
|
function createApi(io2, apiUrl, token) {
|
|
1332
1334
|
async function raw(path, init2 = {}) {
|
|
1333
1335
|
const headers = new Headers(init2.headers);
|
|
1334
1336
|
headers.set("authorization", `Bearer ${token}`);
|
|
1335
|
-
headers.set("user-agent", "hostedge-cli/0.1.
|
|
1337
|
+
headers.set("user-agent", "hostedge-cli/0.1.2");
|
|
1336
1338
|
let response;
|
|
1337
1339
|
try {
|
|
1338
1340
|
response = await io2.fetch(`${apiUrl}${path}`, { ...init2, headers });
|
|
@@ -1343,7 +1345,13 @@ function createApi(io2, apiUrl, token) {
|
|
|
1343
1345
|
const body2 = await response.json().catch(() => ({}));
|
|
1344
1346
|
const fields = body2.error?.fields ? ` ${Object.entries(body2.error.fields).map(([k, v]) => `${k}: ${v}`).join("; ")}` : "";
|
|
1345
1347
|
const message = response.status === 401 ? `${body2.error?.message ?? "Not signed in."} Run \`hostedge login\`.` : `${body2.error?.message ?? `HostEdge answered ${response.status}.`}${fields}`;
|
|
1346
|
-
|
|
1348
|
+
const wait = Number(response.headers.get("retry-after"));
|
|
1349
|
+
throw new ApiError(
|
|
1350
|
+
message,
|
|
1351
|
+
response.status,
|
|
1352
|
+
body2.error?.code ?? null,
|
|
1353
|
+
Number.isFinite(wait) && wait > 0 ? wait : null
|
|
1354
|
+
);
|
|
1347
1355
|
}
|
|
1348
1356
|
return response;
|
|
1349
1357
|
}
|
|
@@ -1566,7 +1574,8 @@ async function withRetries(work, retryable, attempts = 4, delayMs = 500) {
|
|
|
1566
1574
|
return await work();
|
|
1567
1575
|
} catch (error) {
|
|
1568
1576
|
if (attempt >= attempts || !retryable(error)) throw error;
|
|
1569
|
-
|
|
1577
|
+
const wait = typeof delayMs === "function" ? delayMs(error, attempt) : delayMs * 2 ** (attempt - 1);
|
|
1578
|
+
await new Promise((resolve6) => setTimeout(resolve6, wait));
|
|
1570
1579
|
}
|
|
1571
1580
|
}
|
|
1572
1581
|
}
|
|
@@ -1639,8 +1648,31 @@ async function* readSse(body2) {
|
|
|
1639
1648
|
|
|
1640
1649
|
// src/commands/deploy.ts
|
|
1641
1650
|
var FINISHED = /* @__PURE__ */ new Set(["ready", "failed", "canceled"]);
|
|
1642
|
-
var UPLOAD_CONCURRENCY =
|
|
1651
|
+
var UPLOAD_CONCURRENCY = 6;
|
|
1652
|
+
var BATCH_BYTES = 3 * 1024 * 1024;
|
|
1653
|
+
var BATCH_FILES = 100;
|
|
1643
1654
|
var UPLOAD_TIMEOUT_MS = 3e4;
|
|
1655
|
+
function batchesOf(files) {
|
|
1656
|
+
const batches = [];
|
|
1657
|
+
let current = [];
|
|
1658
|
+
let bytes = 0;
|
|
1659
|
+
for (const file of files) {
|
|
1660
|
+
if (current.length > 0 && (bytes + file.size > BATCH_BYTES || current.length >= BATCH_FILES)) {
|
|
1661
|
+
batches.push(current);
|
|
1662
|
+
current = [];
|
|
1663
|
+
bytes = 0;
|
|
1664
|
+
}
|
|
1665
|
+
current.push(file);
|
|
1666
|
+
bytes += file.size;
|
|
1667
|
+
}
|
|
1668
|
+
if (current.length > 0) batches.push(current);
|
|
1669
|
+
return batches;
|
|
1670
|
+
}
|
|
1671
|
+
function retryDelay(error, attempt) {
|
|
1672
|
+
if (error instanceof ApiError && error.retryAfter !== null)
|
|
1673
|
+
return Math.min(6e4, (error.retryAfter + 1) * 1e3);
|
|
1674
|
+
return Math.min(6e4, 500 * 2 ** (attempt - 1));
|
|
1675
|
+
}
|
|
1644
1676
|
function temporary(error) {
|
|
1645
1677
|
if (error instanceof ApiError) return error.status === 429 || error.status >= 500;
|
|
1646
1678
|
return error instanceof CliError;
|
|
@@ -1721,23 +1753,56 @@ async function deploy(r, options) {
|
|
|
1721
1753
|
let uploaded = 0;
|
|
1722
1754
|
let reported = 0;
|
|
1723
1755
|
const step = Math.max(1, Math.ceil(toUpload.length / 10));
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
await withRetries(
|
|
1727
|
-
() => api.raw(`/api/v1/uploads/${file.hash}`, {
|
|
1728
|
-
method: "PUT",
|
|
1729
|
-
headers: { "content-type": "application/octet-stream" },
|
|
1730
|
-
body: bytes,
|
|
1731
|
-
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS)
|
|
1732
|
-
}),
|
|
1733
|
-
temporary
|
|
1734
|
-
);
|
|
1735
|
-
uploaded++;
|
|
1756
|
+
const progress = (count2) => {
|
|
1757
|
+
uploaded += count2;
|
|
1736
1758
|
if (toUpload.length > 20 && uploaded - reported >= step && uploaded < toUpload.length) {
|
|
1737
1759
|
reported = uploaded;
|
|
1738
1760
|
r.say(`${s.dim(` ${uploaded}/${toUpload.length} uploaded`)}
|
|
1739
1761
|
`);
|
|
1740
1762
|
}
|
|
1763
|
+
};
|
|
1764
|
+
const send = (path, init2, bytes) => withRetries(
|
|
1765
|
+
() => api.raw(path, {
|
|
1766
|
+
...init2,
|
|
1767
|
+
// 30 s plus a few more for every MB, so a big file on a slow line can finish.
|
|
1768
|
+
signal: AbortSignal.timeout(UPLOAD_TIMEOUT_MS + Math.ceil(bytes / 1048576) * 4e3)
|
|
1769
|
+
}),
|
|
1770
|
+
temporary,
|
|
1771
|
+
6,
|
|
1772
|
+
retryDelay
|
|
1773
|
+
);
|
|
1774
|
+
let batching = true;
|
|
1775
|
+
await pool(batchesOf(toUpload), UPLOAD_CONCURRENCY, async (batch) => {
|
|
1776
|
+
if (batching && (batch.length > 1 || (batch[0]?.size ?? 0) <= BATCH_BYTES)) {
|
|
1777
|
+
const form = new FormData();
|
|
1778
|
+
for (const file of batch)
|
|
1779
|
+
form.append(file.hash, new Blob([await readFile4(file.absolute)]), file.hash);
|
|
1780
|
+
try {
|
|
1781
|
+
await send(
|
|
1782
|
+
"/api/v1/uploads/batch",
|
|
1783
|
+
{ method: "POST", body: form },
|
|
1784
|
+
batch.reduce((sum, file) => sum + file.size, 0)
|
|
1785
|
+
);
|
|
1786
|
+
progress(batch.length);
|
|
1787
|
+
return;
|
|
1788
|
+
} catch (error) {
|
|
1789
|
+
if (!(error instanceof ApiError) || error.status !== 404 && error.status !== 405)
|
|
1790
|
+
throw error;
|
|
1791
|
+
batching = false;
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
for (const file of batch) {
|
|
1795
|
+
await send(
|
|
1796
|
+
`/api/v1/uploads/${file.hash}`,
|
|
1797
|
+
{
|
|
1798
|
+
method: "PUT",
|
|
1799
|
+
headers: { "content-type": "application/octet-stream" },
|
|
1800
|
+
body: await readFile4(file.absolute)
|
|
1801
|
+
},
|
|
1802
|
+
file.size
|
|
1803
|
+
);
|
|
1804
|
+
progress(1);
|
|
1805
|
+
}
|
|
1741
1806
|
});
|
|
1742
1807
|
let project;
|
|
1743
1808
|
if (!link) {
|
|
@@ -2969,7 +3034,7 @@ var Reporter = class {
|
|
|
2969
3034
|
};
|
|
2970
3035
|
|
|
2971
3036
|
// src/cli.ts
|
|
2972
|
-
var VERSION = "0.1.
|
|
3037
|
+
var VERSION = "0.1.2";
|
|
2973
3038
|
var HELP = `hostedge \u2014 deploy to HostEdge from your terminal
|
|
2974
3039
|
|
|
2975
3040
|
Usage
|