drop2run 0.10.0 → 0.11.0
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
CHANGED
|
@@ -711,7 +711,9 @@ async function prepareDeploy(siteId, files, options, signal) {
|
|
|
711
711
|
}));
|
|
712
712
|
const prepared = await request(
|
|
713
713
|
`sites/${encodeURIComponent(siteId)}/deploys/prepare`,
|
|
714
|
-
|
|
714
|
+
// `batches` asks for batch permits; an API that predates them ignores the field, and one that
|
|
715
|
+
// knows them answers with single permits until they are switched on.
|
|
716
|
+
{ files: manifest, batches: true },
|
|
715
717
|
options,
|
|
716
718
|
signal
|
|
717
719
|
);
|
|
@@ -800,6 +802,47 @@ async function problemToError(response) {
|
|
|
800
802
|
const message = typeof problem.detail === "string" ? problem.detail : typeof problem.title === "string" ? problem.title : `The server responded with ${response.status}.`;
|
|
801
803
|
return new DeployError(code, message, problem);
|
|
802
804
|
}
|
|
805
|
+
function batchLayout(members) {
|
|
806
|
+
return members.map((member) => `${member.sha256} ${member.size}
|
|
807
|
+
`).join("");
|
|
808
|
+
}
|
|
809
|
+
function isBatch(target) {
|
|
810
|
+
return "files" in target;
|
|
811
|
+
}
|
|
812
|
+
function parseBatchResult(body) {
|
|
813
|
+
try {
|
|
814
|
+
const parsed = JSON.parse(body);
|
|
815
|
+
if (parsed === null || typeof parsed !== "object") return null;
|
|
816
|
+
const { stored, present, failed } = parsed;
|
|
817
|
+
if (typeof stored !== "number" || typeof present !== "number" || !Array.isArray(failed))
|
|
818
|
+
return null;
|
|
819
|
+
return { stored, present, failed };
|
|
820
|
+
} catch {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
async function mapInOrder(items, limit, fn, signal) {
|
|
825
|
+
const results = new Array(items.length);
|
|
826
|
+
let next = 0;
|
|
827
|
+
let failure2 = null;
|
|
828
|
+
const lane = async () => {
|
|
829
|
+
for (; ; ) {
|
|
830
|
+
if (failure2 !== null) return;
|
|
831
|
+
const index = next++;
|
|
832
|
+
if (index >= items.length) return;
|
|
833
|
+
try {
|
|
834
|
+
results[index] = await fn(items[index], index);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
failure2 ??= { error };
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
const lanes = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, lane);
|
|
842
|
+
await Promise.all(lanes);
|
|
843
|
+
if (failure2 !== null) throw failure2.error;
|
|
844
|
+
return results;
|
|
845
|
+
}
|
|
803
846
|
const FILE_TYPES = [
|
|
804
847
|
// Documents. The three extensions that make a folder a documents site.
|
|
805
848
|
{
|
|
@@ -1283,13 +1326,46 @@ async function sha256Hex(bytes) {
|
|
|
1283
1326
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
1284
1327
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1285
1328
|
}
|
|
1329
|
+
const HASH_CONCURRENCY = 4;
|
|
1330
|
+
const HASH_BYTES_IN_FLIGHT = 16 * 1024 * 1024;
|
|
1286
1331
|
async function hashAll(files, onProgress, signal) {
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1332
|
+
const budget = byteBudget(HASH_BYTES_IN_FLIGHT);
|
|
1333
|
+
let done = 0;
|
|
1334
|
+
return mapInOrder(
|
|
1335
|
+
files,
|
|
1336
|
+
HASH_CONCURRENCY,
|
|
1337
|
+
async (file) => {
|
|
1338
|
+
const size = sizeOf(file.bytes);
|
|
1339
|
+
await budget.acquire(size);
|
|
1340
|
+
try {
|
|
1341
|
+
signal?.throwIfAborted();
|
|
1342
|
+
const hashed = { ...file, sha256: await sha256Hex(await readForHash(file)) };
|
|
1343
|
+
done += 1;
|
|
1344
|
+
onProgress?.(done, files.length);
|
|
1345
|
+
return hashed;
|
|
1346
|
+
} finally {
|
|
1347
|
+
budget.release(size);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
function byteBudget(limit) {
|
|
1353
|
+
let held = 0;
|
|
1354
|
+
let waiting = [];
|
|
1355
|
+
return {
|
|
1356
|
+
async acquire(size) {
|
|
1357
|
+
while (held > 0 && held + size > limit) {
|
|
1358
|
+
await new Promise((resolve2) => waiting.push(resolve2));
|
|
1359
|
+
}
|
|
1360
|
+
held += size;
|
|
1361
|
+
},
|
|
1362
|
+
release(size) {
|
|
1363
|
+
held -= size;
|
|
1364
|
+
const woken = waiting;
|
|
1365
|
+
waiting = [];
|
|
1366
|
+
for (const wake of woken) wake();
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1293
1369
|
}
|
|
1294
1370
|
async function readForHash(file) {
|
|
1295
1371
|
try {
|
|
@@ -1318,10 +1394,13 @@ const IGNORED_DIRECTORIES = [
|
|
|
1318
1394
|
".codex",
|
|
1319
1395
|
".gemini"
|
|
1320
1396
|
];
|
|
1397
|
+
function isEnvironmentFile(name) {
|
|
1398
|
+
return name === ".env" || name === ".envrc" || name.startsWith(".env.");
|
|
1399
|
+
}
|
|
1321
1400
|
function shouldIgnore(path) {
|
|
1322
1401
|
const segments = path.split("/");
|
|
1323
1402
|
const name = segments.at(-1) ?? "";
|
|
1324
|
-
if (name === "" || IGNORED_EXACT.has(name)) return true;
|
|
1403
|
+
if (name === "" || IGNORED_EXACT.has(name) || isEnvironmentFile(name)) return true;
|
|
1325
1404
|
if (name.startsWith("._")) return true;
|
|
1326
1405
|
return segments.some((segment) => IGNORED_DIRECTORIES.includes(segment));
|
|
1327
1406
|
}
|
|
@@ -1456,9 +1535,7 @@ async function readSiteName(files) {
|
|
|
1456
1535
|
}
|
|
1457
1536
|
}
|
|
1458
1537
|
function decodeHead(bytes) {
|
|
1459
|
-
return new TextDecoder("utf-8", { fatal: false }).decode(
|
|
1460
|
-
bytes.subarray(0, HEAD_BYTES)
|
|
1461
|
-
);
|
|
1538
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(bytes.subarray(0, HEAD_BYTES));
|
|
1462
1539
|
}
|
|
1463
1540
|
function nameFromHtml(html) {
|
|
1464
1541
|
const document = parse(html);
|
|
@@ -1483,9 +1560,7 @@ function parse(html) {
|
|
|
1483
1560
|
}
|
|
1484
1561
|
}
|
|
1485
1562
|
function matchTag(html, tag) {
|
|
1486
|
-
const match = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}\\s*>`, "i").exec(
|
|
1487
|
-
html
|
|
1488
|
-
);
|
|
1563
|
+
const match = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}\\s*>`, "i").exec(html);
|
|
1489
1564
|
return match?.[1] === void 0 ? null : decodeEntities(match[1]);
|
|
1490
1565
|
}
|
|
1491
1566
|
function matchOgSiteName(html) {
|
|
@@ -1586,6 +1661,7 @@ function trimSuffix(title) {
|
|
|
1586
1661
|
return title;
|
|
1587
1662
|
}
|
|
1588
1663
|
const DEFAULT_CONCURRENCY = 8;
|
|
1664
|
+
const MAX_BATCH_CONCURRENCY = 64;
|
|
1589
1665
|
const DEFAULT_BYTES_IN_FLIGHT = 64 * 1024 * 1024;
|
|
1590
1666
|
const DEFAULT_RETRIES = 3;
|
|
1591
1667
|
const BACKOFF_MS = [1e3, 2e3, 4e3];
|
|
@@ -1602,14 +1678,31 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
1602
1678
|
const put = deps.put ?? defaultTransport(deps.fetch);
|
|
1603
1679
|
const delay = deps.delay ?? defaultDelay;
|
|
1604
1680
|
const concurrency = deps.concurrency ?? DEFAULT_CONCURRENCY;
|
|
1681
|
+
const batchConcurrency = Math.max(
|
|
1682
|
+
1,
|
|
1683
|
+
Math.min(MAX_BATCH_CONCURRENCY, Math.floor(deps.batchConcurrency ?? concurrency))
|
|
1684
|
+
);
|
|
1605
1685
|
const retries = deps.retries ?? DEFAULT_RETRIES;
|
|
1606
1686
|
const stallMs = deps.stallMs ?? DEFAULT_STALL_MS;
|
|
1607
1687
|
const now = deps.now ?? Date.now;
|
|
1608
1688
|
const maxBytesInFlight = deps.maxBytesInFlight ?? DEFAULT_BYTES_IN_FLIGHT;
|
|
1609
1689
|
const byPath = new Map(files.map((file) => [file.path, file]));
|
|
1690
|
+
const fileCount = targets.reduce(
|
|
1691
|
+
(sum, target) => sum + (isBatch(target) ? target.files.length : 1),
|
|
1692
|
+
0
|
|
1693
|
+
);
|
|
1610
1694
|
let nextIndex = 0;
|
|
1611
1695
|
let done = 0;
|
|
1612
1696
|
let failed = false;
|
|
1697
|
+
let firstFailure;
|
|
1698
|
+
const local = new AbortController();
|
|
1699
|
+
const inner = local.signal;
|
|
1700
|
+
const fail = (error) => {
|
|
1701
|
+
if (failed) return;
|
|
1702
|
+
failed = true;
|
|
1703
|
+
firstFailure = error;
|
|
1704
|
+
local.abort();
|
|
1705
|
+
};
|
|
1613
1706
|
let reserved = 0;
|
|
1614
1707
|
let waiting = [];
|
|
1615
1708
|
const acquire = async (size) => {
|
|
@@ -1624,17 +1717,32 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
1624
1717
|
waiting = [];
|
|
1625
1718
|
for (const wake of woken) wake();
|
|
1626
1719
|
};
|
|
1720
|
+
const running = { single: 0, batch: 0 };
|
|
1721
|
+
const limit = { single: concurrency, batch: batchConcurrency };
|
|
1722
|
+
let slotWaiting = [];
|
|
1723
|
+
const takeSlot = async (kind) => {
|
|
1724
|
+
while (running[kind] >= limit[kind]) {
|
|
1725
|
+
await new Promise((resolve2) => slotWaiting.push(resolve2));
|
|
1726
|
+
}
|
|
1727
|
+
running[kind] += 1;
|
|
1728
|
+
};
|
|
1729
|
+
const freeSlot = (kind) => {
|
|
1730
|
+
running[kind] -= 1;
|
|
1731
|
+
const woken = slotWaiting;
|
|
1732
|
+
slotWaiting = [];
|
|
1733
|
+
for (const wake of woken) wake();
|
|
1734
|
+
};
|
|
1627
1735
|
let landedBytes = 0;
|
|
1628
1736
|
const inFlight = /* @__PURE__ */ new Map();
|
|
1629
1737
|
let lastReport = Number.NEGATIVE_INFINITY;
|
|
1630
1738
|
const report = (path) => {
|
|
1631
|
-
if (onProgress === void 0) return;
|
|
1739
|
+
if (onProgress === void 0 || failed) return;
|
|
1632
1740
|
const at = now();
|
|
1633
1741
|
if (path === void 0 && at - lastReport < PROGRESS_INTERVAL_MS) return;
|
|
1634
1742
|
lastReport = at;
|
|
1635
1743
|
let bytes = landedBytes;
|
|
1636
1744
|
for (const sent of inFlight.values()) bytes += sent;
|
|
1637
|
-
onProgress(done,
|
|
1745
|
+
onProgress(done, fileCount, bytes, path);
|
|
1638
1746
|
};
|
|
1639
1747
|
const worker = async () => {
|
|
1640
1748
|
for (; ; ) {
|
|
@@ -1642,25 +1750,49 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
1642
1750
|
const index = nextIndex++;
|
|
1643
1751
|
const target = targets[index];
|
|
1644
1752
|
if (target === void 0) return;
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1753
|
+
inner.throwIfAborted();
|
|
1754
|
+
const refs = isBatch(target) ? target.files : [target];
|
|
1755
|
+
const members = [];
|
|
1756
|
+
for (const ref of refs) {
|
|
1757
|
+
const file = byPath.get(ref.path);
|
|
1758
|
+
if (file === void 0) {
|
|
1759
|
+
const missing = new DeployError(
|
|
1760
|
+
ClientErrorCode.UploadFailed,
|
|
1761
|
+
`The server asked for ${ref.path}, which is not in the manifest.`,
|
|
1762
|
+
{ path: ref.path }
|
|
1763
|
+
);
|
|
1764
|
+
fail(missing);
|
|
1765
|
+
throw missing;
|
|
1766
|
+
}
|
|
1767
|
+
members.push(file);
|
|
1653
1768
|
}
|
|
1654
|
-
const
|
|
1655
|
-
|
|
1769
|
+
const layout = isBatch(target) ? new TextEncoder().encode(
|
|
1770
|
+
batchLayout(
|
|
1771
|
+
target.files.map((ref, i) => ({
|
|
1772
|
+
sha256: ref.sha256,
|
|
1773
|
+
size: sizeOf(members[i].bytes)
|
|
1774
|
+
}))
|
|
1775
|
+
)
|
|
1776
|
+
) : null;
|
|
1777
|
+
const size = members.reduce((sum, file) => sum + sizeOf(file.bytes), layout?.length ?? 0);
|
|
1778
|
+
const copied = isBatch(target) ? members.reduce(
|
|
1779
|
+
(sum, file) => sum + (file.bytes instanceof Blob ? 0 : sizeOf(file.bytes)),
|
|
1780
|
+
0
|
|
1781
|
+
) : 0;
|
|
1782
|
+
const reservation = size + copied;
|
|
1783
|
+
const first = members[0].path;
|
|
1784
|
+
const label = members.length === 1 ? first : `${first} and ${members.length - 1} more`;
|
|
1785
|
+
const kind = isBatch(target) ? "batch" : "single";
|
|
1786
|
+
await takeSlot(kind);
|
|
1787
|
+
await acquire(reservation);
|
|
1656
1788
|
try {
|
|
1657
1789
|
if (failed) return;
|
|
1658
|
-
|
|
1659
|
-
|
|
1790
|
+
inner.throwIfAborted();
|
|
1791
|
+
const context = {
|
|
1660
1792
|
put,
|
|
1661
1793
|
delay,
|
|
1662
1794
|
retries,
|
|
1663
|
-
signal,
|
|
1795
|
+
signal: inner,
|
|
1664
1796
|
uploadUrl,
|
|
1665
1797
|
stallMs,
|
|
1666
1798
|
onSent: (sent) => {
|
|
@@ -1669,26 +1801,34 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
1669
1801
|
},
|
|
1670
1802
|
onRetry: (attempt, stalled) => {
|
|
1671
1803
|
inFlight.set(index, 0);
|
|
1672
|
-
onRetry?.(
|
|
1804
|
+
onRetry?.(label, attempt, retries + 1, stalled);
|
|
1673
1805
|
}
|
|
1674
|
-
}
|
|
1806
|
+
};
|
|
1807
|
+
if (isBatch(target) && layout !== null)
|
|
1808
|
+
await uploadBatchOne(target, members, layout, context);
|
|
1809
|
+
else await uploadOne(target, members[0], context);
|
|
1675
1810
|
} catch (error) {
|
|
1676
|
-
|
|
1811
|
+
fail(error);
|
|
1677
1812
|
throw error;
|
|
1678
1813
|
} finally {
|
|
1679
|
-
release(
|
|
1814
|
+
release(reservation);
|
|
1815
|
+
freeSlot(kind);
|
|
1680
1816
|
}
|
|
1681
1817
|
inFlight.delete(index);
|
|
1682
|
-
done +=
|
|
1818
|
+
done += members.length;
|
|
1683
1819
|
landedBytes += size;
|
|
1684
|
-
report(
|
|
1820
|
+
report(members.at(-1).path);
|
|
1685
1821
|
}
|
|
1686
1822
|
};
|
|
1687
|
-
const workers = Array.from(
|
|
1823
|
+
const workers = Array.from(
|
|
1824
|
+
{ length: Math.min(Math.max(concurrency, batchConcurrency), targets.length) },
|
|
1825
|
+
worker
|
|
1826
|
+
);
|
|
1688
1827
|
try {
|
|
1689
1828
|
await Promise.all(workers);
|
|
1690
1829
|
} catch (error) {
|
|
1691
|
-
throw asDeployError(error);
|
|
1830
|
+
throw asDeployError(firstFailure ?? error);
|
|
1831
|
+
} finally {
|
|
1692
1832
|
}
|
|
1693
1833
|
}
|
|
1694
1834
|
async function uploadOne(target, file, context) {
|
|
@@ -1701,7 +1841,7 @@ async function uploadOne(target, file, context) {
|
|
|
1701
1841
|
context.signal?.throwIfAborted();
|
|
1702
1842
|
}
|
|
1703
1843
|
try {
|
|
1704
|
-
const
|
|
1844
|
+
const answer = await context.put({
|
|
1705
1845
|
url: context.uploadUrl,
|
|
1706
1846
|
token: target.token,
|
|
1707
1847
|
body: untyped(file.bytes),
|
|
@@ -1709,6 +1849,7 @@ async function uploadOne(target, file, context) {
|
|
|
1709
1849
|
onSent: context.onSent,
|
|
1710
1850
|
stallMs: context.stallMs
|
|
1711
1851
|
});
|
|
1852
|
+
const status = typeof answer === "number" ? answer : answer.status;
|
|
1712
1853
|
if (status >= 200 && status < 300) return;
|
|
1713
1854
|
if (status === 401 || status === 403 || status === 429) {
|
|
1714
1855
|
throw new DeployError(
|
|
@@ -1737,6 +1878,85 @@ async function uploadOne(target, file, context) {
|
|
|
1737
1878
|
cause: describeError(lastError)
|
|
1738
1879
|
});
|
|
1739
1880
|
}
|
|
1881
|
+
async function uploadBatchOne(batch, members, layout, context) {
|
|
1882
|
+
const first = members[0].path;
|
|
1883
|
+
const label = members.length === 1 ? first : `${first} and ${members.length - 1} more`;
|
|
1884
|
+
let lastError;
|
|
1885
|
+
for (let attempt = 0; attempt <= context.retries; attempt++) {
|
|
1886
|
+
context.signal?.throwIfAborted();
|
|
1887
|
+
if (attempt > 0) {
|
|
1888
|
+
context.onRetry(attempt + 1, lastError instanceof StalledError);
|
|
1889
|
+
await context.delay(BACKOFF_MS[attempt - 1] ?? BACKOFF_MS.at(-1) ?? 1e3);
|
|
1890
|
+
context.signal?.throwIfAborted();
|
|
1891
|
+
}
|
|
1892
|
+
try {
|
|
1893
|
+
const body = new Blob([layout, ...members.map((file) => file.bytes)]);
|
|
1894
|
+
const answer = await context.put({
|
|
1895
|
+
url: context.uploadUrl,
|
|
1896
|
+
token: batch.token,
|
|
1897
|
+
body,
|
|
1898
|
+
signal: context.signal,
|
|
1899
|
+
onSent: context.onSent,
|
|
1900
|
+
stallMs: context.stallMs,
|
|
1901
|
+
wantsBody: true
|
|
1902
|
+
});
|
|
1903
|
+
const status = typeof answer === "number" ? answer : answer.status;
|
|
1904
|
+
if (status >= 200 && status < 300) {
|
|
1905
|
+
const result = typeof answer === "number" ? null : parseBatchResult(answer.body);
|
|
1906
|
+
if (result === null) {
|
|
1907
|
+
lastError = new Error(
|
|
1908
|
+
"The upload service answered with something that is not a batch result."
|
|
1909
|
+
);
|
|
1910
|
+
continue;
|
|
1911
|
+
}
|
|
1912
|
+
if (result.failed.length === 0) return;
|
|
1913
|
+
const changed = result.failed.find((failure2) => failure2.reason === "hash_mismatch");
|
|
1914
|
+
if (changed !== void 0) {
|
|
1915
|
+
const path = members[changed.i]?.path ?? first;
|
|
1916
|
+
throw new DeployError(
|
|
1917
|
+
ClientErrorCode.ReadFailed,
|
|
1918
|
+
`${path} changed after it was read, so it no longer matches what was checked. Drop the folder again.`,
|
|
1919
|
+
{ path }
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
lastError = new Error(
|
|
1923
|
+
`${result.failed.length} of ${members.length} files were not stored.`
|
|
1924
|
+
);
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
if ([400, 401, 403, 411, 413, 429].includes(status)) {
|
|
1928
|
+
throw new DeployError(ClientErrorCode.UploadFailed, refusalMessage(status, label), {
|
|
1929
|
+
path: first,
|
|
1930
|
+
status
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
lastError = new Error(`HTTP ${status}`);
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
if (error instanceof DeployError) throw error;
|
|
1936
|
+
if (isAbort(error)) throw cancelled();
|
|
1937
|
+
lastError = error;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
const message = lastError instanceof StalledError ? `Upload of ${label} stopped moving and did not recover after ${context.retries + 1} attempts. Check your connection and try again.` : `Could not upload ${label} after ${context.retries + 1} attempts.`;
|
|
1941
|
+
throw new DeployError(ClientErrorCode.UploadFailed, message, {
|
|
1942
|
+
path: first,
|
|
1943
|
+
cause: describeError(lastError)
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
function refusalMessage(status, label) {
|
|
1947
|
+
switch (status) {
|
|
1948
|
+
case 401:
|
|
1949
|
+
return `Upload of ${label} was refused because its upload window expired. Try deploying again.`;
|
|
1950
|
+
case 429:
|
|
1951
|
+
return `Upload of ${label} was refused: this deploy has used every upload it was given. Try deploying again.`;
|
|
1952
|
+
case 400:
|
|
1953
|
+
case 411:
|
|
1954
|
+
case 413:
|
|
1955
|
+
return `Upload of ${label} was refused because it no longer matches what was checked — a file may have changed since you dropped it. Drop the folder again.`;
|
|
1956
|
+
default:
|
|
1957
|
+
return `Upload of ${label} was refused by the upload service. Try deploying again.`;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1740
1960
|
function untyped(content) {
|
|
1741
1961
|
if (ArrayBuffer.isView(content) || content.type === "") return content;
|
|
1742
1962
|
return content.slice(0, content.size);
|
|
@@ -1747,7 +1967,8 @@ function defaultTransport(doFetch) {
|
|
|
1747
1967
|
return Xhr === void 0 ? fetchTransport(globalFetch()) : xhrTransport(Xhr);
|
|
1748
1968
|
}
|
|
1749
1969
|
function fetchTransport(doFetch) {
|
|
1750
|
-
return async (
|
|
1970
|
+
return async (request2) => {
|
|
1971
|
+
const { url, token, body, signal } = request2;
|
|
1751
1972
|
const response = await doFetch(url, {
|
|
1752
1973
|
method: "PUT",
|
|
1753
1974
|
// The view, not a copy. Wrapping it in `new Blob([body.slice().buffer])` made two extra
|
|
@@ -1762,11 +1983,11 @@ function fetchTransport(doFetch) {
|
|
|
1762
1983
|
headers: { Authorization: `Bearer ${token}` },
|
|
1763
1984
|
...signal ? { signal } : {}
|
|
1764
1985
|
});
|
|
1765
|
-
return response.status;
|
|
1986
|
+
return request2.wantsBody === true ? { status: response.status, body: await response.text() } : response.status;
|
|
1766
1987
|
};
|
|
1767
1988
|
}
|
|
1768
1989
|
function xhrTransport(Xhr) {
|
|
1769
|
-
return ({ url, token, body, signal, onSent, stallMs }) => new Promise((resolve2, reject) => {
|
|
1990
|
+
return ({ url, token, body, signal, onSent, stallMs, wantsBody }) => new Promise((resolve2, reject) => {
|
|
1770
1991
|
const xhr = new Xhr();
|
|
1771
1992
|
let settled = false;
|
|
1772
1993
|
let timer;
|
|
@@ -1800,7 +2021,11 @@ function xhrTransport(Xhr) {
|
|
|
1800
2021
|
onSent(event.loaded);
|
|
1801
2022
|
};
|
|
1802
2023
|
xhr.onprogress = arm;
|
|
1803
|
-
xhr.onload = () => settle(
|
|
2024
|
+
xhr.onload = () => settle(
|
|
2025
|
+
() => resolve2(
|
|
2026
|
+
wantsBody === true ? { status: xhr.status, body: xhr.responseText ?? "" } : xhr.status
|
|
2027
|
+
)
|
|
2028
|
+
);
|
|
1804
2029
|
xhr.onerror = () => settle(() => reject(new TypeError("Network error during upload.")));
|
|
1805
2030
|
arm();
|
|
1806
2031
|
xhr.send(body);
|
|
@@ -1865,11 +2090,16 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
|
|
|
1865
2090
|
return;
|
|
1866
2091
|
}
|
|
1867
2092
|
const sizes = new Map(hashed.map((file) => [file.path, sizeOf(file.bytes)]));
|
|
1868
|
-
const
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2093
|
+
const batches = prepared.batches ?? [];
|
|
2094
|
+
const targets = [...prepared.upload, ...batches];
|
|
2095
|
+
const totalBytes = prepared.upload.reduce((sum, target) => sum + (sizes.get(target.path) ?? 0), 0) + batches.reduce((sum, batch) => {
|
|
2096
|
+
const members = batch.files.map((ref) => ({
|
|
2097
|
+
sha256: ref.sha256,
|
|
2098
|
+
size: sizes.get(ref.path) ?? 0
|
|
2099
|
+
}));
|
|
2100
|
+
return sum + batchLayout(members).length + members.reduce((n, member) => n + member.size, 0);
|
|
2101
|
+
}, 0);
|
|
2102
|
+
if (targets.length > 0) {
|
|
1873
2103
|
onProgress({
|
|
1874
2104
|
type: "uploading",
|
|
1875
2105
|
done: prepared.reused,
|
|
@@ -1880,7 +2110,7 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
|
|
|
1880
2110
|
});
|
|
1881
2111
|
}
|
|
1882
2112
|
await uploadAll(
|
|
1883
|
-
|
|
2113
|
+
targets,
|
|
1884
2114
|
prepared.uploadUrl,
|
|
1885
2115
|
hashed,
|
|
1886
2116
|
(done, _total, bytes, path) => onProgress({
|
|
@@ -1893,7 +2123,9 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
|
|
|
1893
2123
|
...path === void 0 ? {} : { path }
|
|
1894
2124
|
}),
|
|
1895
2125
|
signal,
|
|
1896
|
-
|
|
2126
|
+
// The server's number unless the caller set one: it is tuned on the server so a deploy of many
|
|
2127
|
+
// small files speeds up without a CLI or MCP release.
|
|
2128
|
+
typeof prepared.batchConcurrency === "number" && options.upload?.batchConcurrency === void 0 ? { ...options.upload, batchConcurrency: prepared.batchConcurrency } : options.upload,
|
|
1897
2129
|
(path, attempt, attempts, stalled) => onProgress({ type: "retrying", path, attempt, attempts, stalled })
|
|
1898
2130
|
);
|
|
1899
2131
|
onProgress({ type: "completing", reused: prepared.reused });
|
|
@@ -3058,7 +3290,7 @@ async function run(commandLine) {
|
|
|
3058
3290
|
const positional = [...positionals(argv), ...literal];
|
|
3059
3291
|
const [command, ...rest] = positional;
|
|
3060
3292
|
if (argv.includes("--version")) {
|
|
3061
|
-
const { version } = await import("./package-
|
|
3293
|
+
const { version } = await import("./package-B79z0E4v.js").then(
|
|
3062
3294
|
(module) => module.default
|
|
3063
3295
|
);
|
|
3064
3296
|
return { text: version, json: { version }, code: 0 };
|