@drop2run/mcp 0.11.0 → 0.12.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 +282 -50
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -653,7 +653,9 @@ async function prepareDeploy(siteId, files, options, signal) {
|
|
|
653
653
|
}));
|
|
654
654
|
const prepared = await request(
|
|
655
655
|
`sites/${encodeURIComponent(siteId)}/deploys/prepare`,
|
|
656
|
-
|
|
656
|
+
// `batches` asks for batch permits; an API that predates them ignores the field, and one that
|
|
657
|
+
// knows them answers with single permits until they are switched on.
|
|
658
|
+
{ files: manifest, batches: true },
|
|
657
659
|
options,
|
|
658
660
|
signal
|
|
659
661
|
);
|
|
@@ -742,6 +744,47 @@ async function problemToError(response) {
|
|
|
742
744
|
const message = typeof problem.detail === "string" ? problem.detail : typeof problem.title === "string" ? problem.title : `The server responded with ${response.status}.`;
|
|
743
745
|
return new DeployError(code, message, problem);
|
|
744
746
|
}
|
|
747
|
+
function batchLayout(members) {
|
|
748
|
+
return members.map((member) => `${member.sha256} ${member.size}
|
|
749
|
+
`).join("");
|
|
750
|
+
}
|
|
751
|
+
function isBatch(target) {
|
|
752
|
+
return "files" in target;
|
|
753
|
+
}
|
|
754
|
+
function parseBatchResult(body) {
|
|
755
|
+
try {
|
|
756
|
+
const parsed = JSON.parse(body);
|
|
757
|
+
if (parsed === null || typeof parsed !== "object") return null;
|
|
758
|
+
const { stored, present, failed } = parsed;
|
|
759
|
+
if (typeof stored !== "number" || typeof present !== "number" || !Array.isArray(failed))
|
|
760
|
+
return null;
|
|
761
|
+
return { stored, present, failed };
|
|
762
|
+
} catch {
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
async function mapInOrder(items, limit, fn, signal) {
|
|
767
|
+
const results = new Array(items.length);
|
|
768
|
+
let next = 0;
|
|
769
|
+
let failure = null;
|
|
770
|
+
const lane = async () => {
|
|
771
|
+
for (; ; ) {
|
|
772
|
+
if (failure !== null) return;
|
|
773
|
+
const index = next++;
|
|
774
|
+
if (index >= items.length) return;
|
|
775
|
+
try {
|
|
776
|
+
results[index] = await fn(items[index], index);
|
|
777
|
+
} catch (error) {
|
|
778
|
+
failure ??= { error };
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
const lanes = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, lane);
|
|
784
|
+
await Promise.all(lanes);
|
|
785
|
+
if (failure !== null) throw failure.error;
|
|
786
|
+
return results;
|
|
787
|
+
}
|
|
745
788
|
const FILE_TYPES = [
|
|
746
789
|
// Documents. The three extensions that make a folder a documents site.
|
|
747
790
|
{
|
|
@@ -1225,13 +1268,46 @@ async function sha256Hex(bytes) {
|
|
|
1225
1268
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
1226
1269
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1227
1270
|
}
|
|
1271
|
+
const HASH_CONCURRENCY = 4;
|
|
1272
|
+
const HASH_BYTES_IN_FLIGHT = 16 * 1024 * 1024;
|
|
1228
1273
|
async function hashAll(files, onProgress, signal) {
|
|
1229
|
-
const
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1274
|
+
const budget = byteBudget(HASH_BYTES_IN_FLIGHT);
|
|
1275
|
+
let done = 0;
|
|
1276
|
+
return mapInOrder(
|
|
1277
|
+
files,
|
|
1278
|
+
HASH_CONCURRENCY,
|
|
1279
|
+
async (file) => {
|
|
1280
|
+
const size = sizeOf(file.bytes);
|
|
1281
|
+
await budget.acquire(size);
|
|
1282
|
+
try {
|
|
1283
|
+
signal?.throwIfAborted();
|
|
1284
|
+
const hashed = { ...file, sha256: await sha256Hex(await readForHash(file)) };
|
|
1285
|
+
done += 1;
|
|
1286
|
+
onProgress?.(done, files.length);
|
|
1287
|
+
return hashed;
|
|
1288
|
+
} finally {
|
|
1289
|
+
budget.release(size);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
function byteBudget(limit) {
|
|
1295
|
+
let held = 0;
|
|
1296
|
+
let waiting = [];
|
|
1297
|
+
return {
|
|
1298
|
+
async acquire(size) {
|
|
1299
|
+
while (held > 0 && held + size > limit) {
|
|
1300
|
+
await new Promise((resolve) => waiting.push(resolve));
|
|
1301
|
+
}
|
|
1302
|
+
held += size;
|
|
1303
|
+
},
|
|
1304
|
+
release(size) {
|
|
1305
|
+
held -= size;
|
|
1306
|
+
const woken = waiting;
|
|
1307
|
+
waiting = [];
|
|
1308
|
+
for (const wake of woken) wake();
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1235
1311
|
}
|
|
1236
1312
|
async function readForHash(file) {
|
|
1237
1313
|
try {
|
|
@@ -1260,10 +1336,13 @@ const IGNORED_DIRECTORIES = [
|
|
|
1260
1336
|
".codex",
|
|
1261
1337
|
".gemini"
|
|
1262
1338
|
];
|
|
1339
|
+
function isEnvironmentFile(name) {
|
|
1340
|
+
return name === ".env" || name === ".envrc" || name.startsWith(".env.");
|
|
1341
|
+
}
|
|
1263
1342
|
function shouldIgnore(path) {
|
|
1264
1343
|
const segments = path.split("/");
|
|
1265
1344
|
const name = segments.at(-1) ?? "";
|
|
1266
|
-
if (name === "" || IGNORED_EXACT.has(name)) return true;
|
|
1345
|
+
if (name === "" || IGNORED_EXACT.has(name) || isEnvironmentFile(name)) return true;
|
|
1267
1346
|
if (name.startsWith("._")) return true;
|
|
1268
1347
|
return segments.some((segment) => IGNORED_DIRECTORIES.includes(segment));
|
|
1269
1348
|
}
|
|
@@ -1398,9 +1477,7 @@ async function readSiteName(files) {
|
|
|
1398
1477
|
}
|
|
1399
1478
|
}
|
|
1400
1479
|
function decodeHead(bytes) {
|
|
1401
|
-
return new TextDecoder("utf-8", { fatal: false }).decode(
|
|
1402
|
-
bytes.subarray(0, HEAD_BYTES)
|
|
1403
|
-
);
|
|
1480
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(bytes.subarray(0, HEAD_BYTES));
|
|
1404
1481
|
}
|
|
1405
1482
|
function nameFromHtml(html) {
|
|
1406
1483
|
const document = parse(html);
|
|
@@ -1425,9 +1502,7 @@ function parse(html) {
|
|
|
1425
1502
|
}
|
|
1426
1503
|
}
|
|
1427
1504
|
function matchTag(html, tag) {
|
|
1428
|
-
const match = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}\\s*>`, "i").exec(
|
|
1429
|
-
html
|
|
1430
|
-
);
|
|
1505
|
+
const match = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}\\s*>`, "i").exec(html);
|
|
1431
1506
|
return match?.[1] === void 0 ? null : decodeEntities(match[1]);
|
|
1432
1507
|
}
|
|
1433
1508
|
function matchOgSiteName(html) {
|
|
@@ -1528,6 +1603,7 @@ function trimSuffix(title) {
|
|
|
1528
1603
|
return title;
|
|
1529
1604
|
}
|
|
1530
1605
|
const DEFAULT_CONCURRENCY = 8;
|
|
1606
|
+
const MAX_BATCH_CONCURRENCY = 64;
|
|
1531
1607
|
const DEFAULT_BYTES_IN_FLIGHT = 64 * 1024 * 1024;
|
|
1532
1608
|
const DEFAULT_RETRIES = 3;
|
|
1533
1609
|
const BACKOFF_MS = [1e3, 2e3, 4e3];
|
|
@@ -1544,14 +1620,31 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
1544
1620
|
const put = deps.put ?? defaultTransport(deps.fetch);
|
|
1545
1621
|
const delay = deps.delay ?? defaultDelay;
|
|
1546
1622
|
const concurrency = deps.concurrency ?? DEFAULT_CONCURRENCY;
|
|
1623
|
+
const batchConcurrency = Math.max(
|
|
1624
|
+
1,
|
|
1625
|
+
Math.min(MAX_BATCH_CONCURRENCY, Math.floor(deps.batchConcurrency ?? concurrency))
|
|
1626
|
+
);
|
|
1547
1627
|
const retries = deps.retries ?? DEFAULT_RETRIES;
|
|
1548
1628
|
const stallMs = deps.stallMs ?? DEFAULT_STALL_MS;
|
|
1549
1629
|
const now = deps.now ?? Date.now;
|
|
1550
1630
|
const maxBytesInFlight = deps.maxBytesInFlight ?? DEFAULT_BYTES_IN_FLIGHT;
|
|
1551
1631
|
const byPath = new Map(files.map((file) => [file.path, file]));
|
|
1632
|
+
const fileCount = targets.reduce(
|
|
1633
|
+
(sum, target) => sum + (isBatch(target) ? target.files.length : 1),
|
|
1634
|
+
0
|
|
1635
|
+
);
|
|
1552
1636
|
let nextIndex = 0;
|
|
1553
1637
|
let done = 0;
|
|
1554
1638
|
let failed = false;
|
|
1639
|
+
let firstFailure;
|
|
1640
|
+
const local = new AbortController();
|
|
1641
|
+
const inner = local.signal;
|
|
1642
|
+
const fail = (error) => {
|
|
1643
|
+
if (failed) return;
|
|
1644
|
+
failed = true;
|
|
1645
|
+
firstFailure = error;
|
|
1646
|
+
local.abort();
|
|
1647
|
+
};
|
|
1555
1648
|
let reserved = 0;
|
|
1556
1649
|
let waiting = [];
|
|
1557
1650
|
const acquire = async (size) => {
|
|
@@ -1566,17 +1659,32 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
1566
1659
|
waiting = [];
|
|
1567
1660
|
for (const wake of woken) wake();
|
|
1568
1661
|
};
|
|
1662
|
+
const running = { single: 0, batch: 0 };
|
|
1663
|
+
const limit = { single: concurrency, batch: batchConcurrency };
|
|
1664
|
+
let slotWaiting = [];
|
|
1665
|
+
const takeSlot = async (kind) => {
|
|
1666
|
+
while (running[kind] >= limit[kind]) {
|
|
1667
|
+
await new Promise((resolve) => slotWaiting.push(resolve));
|
|
1668
|
+
}
|
|
1669
|
+
running[kind] += 1;
|
|
1670
|
+
};
|
|
1671
|
+
const freeSlot = (kind) => {
|
|
1672
|
+
running[kind] -= 1;
|
|
1673
|
+
const woken = slotWaiting;
|
|
1674
|
+
slotWaiting = [];
|
|
1675
|
+
for (const wake of woken) wake();
|
|
1676
|
+
};
|
|
1569
1677
|
let landedBytes = 0;
|
|
1570
1678
|
const inFlight = /* @__PURE__ */ new Map();
|
|
1571
1679
|
let lastReport = Number.NEGATIVE_INFINITY;
|
|
1572
1680
|
const report2 = (path) => {
|
|
1573
|
-
if (onProgress === void 0) return;
|
|
1681
|
+
if (onProgress === void 0 || failed) return;
|
|
1574
1682
|
const at = now();
|
|
1575
1683
|
if (path === void 0 && at - lastReport < PROGRESS_INTERVAL_MS) return;
|
|
1576
1684
|
lastReport = at;
|
|
1577
1685
|
let bytes = landedBytes;
|
|
1578
1686
|
for (const sent of inFlight.values()) bytes += sent;
|
|
1579
|
-
onProgress(done,
|
|
1687
|
+
onProgress(done, fileCount, bytes, path);
|
|
1580
1688
|
};
|
|
1581
1689
|
const worker = async () => {
|
|
1582
1690
|
for (; ; ) {
|
|
@@ -1584,25 +1692,49 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
1584
1692
|
const index = nextIndex++;
|
|
1585
1693
|
const target = targets[index];
|
|
1586
1694
|
if (target === void 0) return;
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1695
|
+
inner.throwIfAborted();
|
|
1696
|
+
const refs = isBatch(target) ? target.files : [target];
|
|
1697
|
+
const members = [];
|
|
1698
|
+
for (const ref of refs) {
|
|
1699
|
+
const file = byPath.get(ref.path);
|
|
1700
|
+
if (file === void 0) {
|
|
1701
|
+
const missing = new DeployError(
|
|
1702
|
+
ClientErrorCode.UploadFailed,
|
|
1703
|
+
`The server asked for ${ref.path}, which is not in the manifest.`,
|
|
1704
|
+
{ path: ref.path }
|
|
1705
|
+
);
|
|
1706
|
+
fail(missing);
|
|
1707
|
+
throw missing;
|
|
1708
|
+
}
|
|
1709
|
+
members.push(file);
|
|
1595
1710
|
}
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1711
|
+
const layout = isBatch(target) ? new TextEncoder().encode(
|
|
1712
|
+
batchLayout(
|
|
1713
|
+
target.files.map((ref, i) => ({
|
|
1714
|
+
sha256: ref.sha256,
|
|
1715
|
+
size: sizeOf(members[i].bytes)
|
|
1716
|
+
}))
|
|
1717
|
+
)
|
|
1718
|
+
) : null;
|
|
1719
|
+
const size = members.reduce((sum, file) => sum + sizeOf(file.bytes), layout?.length ?? 0);
|
|
1720
|
+
const copied = isBatch(target) ? members.reduce(
|
|
1721
|
+
(sum, file) => sum + (file.bytes instanceof Blob ? 0 : sizeOf(file.bytes)),
|
|
1722
|
+
0
|
|
1723
|
+
) : 0;
|
|
1724
|
+
const reservation = size + copied;
|
|
1725
|
+
const first = members[0].path;
|
|
1726
|
+
const label = members.length === 1 ? first : `${first} and ${members.length - 1} more`;
|
|
1727
|
+
const kind = isBatch(target) ? "batch" : "single";
|
|
1728
|
+
await takeSlot(kind);
|
|
1729
|
+
await acquire(reservation);
|
|
1598
1730
|
try {
|
|
1599
1731
|
if (failed) return;
|
|
1600
|
-
|
|
1601
|
-
|
|
1732
|
+
inner.throwIfAborted();
|
|
1733
|
+
const context = {
|
|
1602
1734
|
put,
|
|
1603
1735
|
delay,
|
|
1604
1736
|
retries,
|
|
1605
|
-
signal,
|
|
1737
|
+
signal: inner,
|
|
1606
1738
|
uploadUrl,
|
|
1607
1739
|
stallMs,
|
|
1608
1740
|
onSent: (sent) => {
|
|
@@ -1611,26 +1743,34 @@ async function uploadAll(targets, uploadUrl, files, onProgress, signal, deps = {
|
|
|
1611
1743
|
},
|
|
1612
1744
|
onRetry: (attempt2, stalled) => {
|
|
1613
1745
|
inFlight.set(index, 0);
|
|
1614
|
-
onRetry?.(
|
|
1746
|
+
onRetry?.(label, attempt2, retries + 1, stalled);
|
|
1615
1747
|
}
|
|
1616
|
-
}
|
|
1748
|
+
};
|
|
1749
|
+
if (isBatch(target) && layout !== null)
|
|
1750
|
+
await uploadBatchOne(target, members, layout, context);
|
|
1751
|
+
else await uploadOne(target, members[0], context);
|
|
1617
1752
|
} catch (error) {
|
|
1618
|
-
|
|
1753
|
+
fail(error);
|
|
1619
1754
|
throw error;
|
|
1620
1755
|
} finally {
|
|
1621
|
-
release(
|
|
1756
|
+
release(reservation);
|
|
1757
|
+
freeSlot(kind);
|
|
1622
1758
|
}
|
|
1623
1759
|
inFlight.delete(index);
|
|
1624
|
-
done +=
|
|
1760
|
+
done += members.length;
|
|
1625
1761
|
landedBytes += size;
|
|
1626
|
-
report2(
|
|
1762
|
+
report2(members.at(-1).path);
|
|
1627
1763
|
}
|
|
1628
1764
|
};
|
|
1629
|
-
const workers = Array.from(
|
|
1765
|
+
const workers = Array.from(
|
|
1766
|
+
{ length: Math.min(Math.max(concurrency, batchConcurrency), targets.length) },
|
|
1767
|
+
worker
|
|
1768
|
+
);
|
|
1630
1769
|
try {
|
|
1631
1770
|
await Promise.all(workers);
|
|
1632
1771
|
} catch (error) {
|
|
1633
|
-
throw asDeployError(error);
|
|
1772
|
+
throw asDeployError(firstFailure ?? error);
|
|
1773
|
+
} finally {
|
|
1634
1774
|
}
|
|
1635
1775
|
}
|
|
1636
1776
|
async function uploadOne(target, file, context) {
|
|
@@ -1643,7 +1783,7 @@ async function uploadOne(target, file, context) {
|
|
|
1643
1783
|
context.signal?.throwIfAborted();
|
|
1644
1784
|
}
|
|
1645
1785
|
try {
|
|
1646
|
-
const
|
|
1786
|
+
const answer = await context.put({
|
|
1647
1787
|
url: context.uploadUrl,
|
|
1648
1788
|
token: target.token,
|
|
1649
1789
|
body: untyped(file.bytes),
|
|
@@ -1651,6 +1791,7 @@ async function uploadOne(target, file, context) {
|
|
|
1651
1791
|
onSent: context.onSent,
|
|
1652
1792
|
stallMs: context.stallMs
|
|
1653
1793
|
});
|
|
1794
|
+
const status = typeof answer === "number" ? answer : answer.status;
|
|
1654
1795
|
if (status >= 200 && status < 300) return;
|
|
1655
1796
|
if (status === 401 || status === 403 || status === 429) {
|
|
1656
1797
|
throw new DeployError(
|
|
@@ -1679,6 +1820,85 @@ async function uploadOne(target, file, context) {
|
|
|
1679
1820
|
cause: describeError(lastError)
|
|
1680
1821
|
});
|
|
1681
1822
|
}
|
|
1823
|
+
async function uploadBatchOne(batch, members, layout, context) {
|
|
1824
|
+
const first = members[0].path;
|
|
1825
|
+
const label = members.length === 1 ? first : `${first} and ${members.length - 1} more`;
|
|
1826
|
+
let lastError;
|
|
1827
|
+
for (let attempt2 = 0; attempt2 <= context.retries; attempt2++) {
|
|
1828
|
+
context.signal?.throwIfAborted();
|
|
1829
|
+
if (attempt2 > 0) {
|
|
1830
|
+
context.onRetry(attempt2 + 1, lastError instanceof StalledError);
|
|
1831
|
+
await context.delay(BACKOFF_MS[attempt2 - 1] ?? BACKOFF_MS.at(-1) ?? 1e3);
|
|
1832
|
+
context.signal?.throwIfAborted();
|
|
1833
|
+
}
|
|
1834
|
+
try {
|
|
1835
|
+
const body = new Blob([layout, ...members.map((file) => file.bytes)]);
|
|
1836
|
+
const answer = await context.put({
|
|
1837
|
+
url: context.uploadUrl,
|
|
1838
|
+
token: batch.token,
|
|
1839
|
+
body,
|
|
1840
|
+
signal: context.signal,
|
|
1841
|
+
onSent: context.onSent,
|
|
1842
|
+
stallMs: context.stallMs,
|
|
1843
|
+
wantsBody: true
|
|
1844
|
+
});
|
|
1845
|
+
const status = typeof answer === "number" ? answer : answer.status;
|
|
1846
|
+
if (status >= 200 && status < 300) {
|
|
1847
|
+
const result = typeof answer === "number" ? null : parseBatchResult(answer.body);
|
|
1848
|
+
if (result === null) {
|
|
1849
|
+
lastError = new Error(
|
|
1850
|
+
"The upload service answered with something that is not a batch result."
|
|
1851
|
+
);
|
|
1852
|
+
continue;
|
|
1853
|
+
}
|
|
1854
|
+
if (result.failed.length === 0) return;
|
|
1855
|
+
const changed = result.failed.find((failure) => failure.reason === "hash_mismatch");
|
|
1856
|
+
if (changed !== void 0) {
|
|
1857
|
+
const path = members[changed.i]?.path ?? first;
|
|
1858
|
+
throw new DeployError(
|
|
1859
|
+
ClientErrorCode.ReadFailed,
|
|
1860
|
+
`${path} changed after it was read, so it no longer matches what was checked. Drop the folder again.`,
|
|
1861
|
+
{ path }
|
|
1862
|
+
);
|
|
1863
|
+
}
|
|
1864
|
+
lastError = new Error(
|
|
1865
|
+
`${result.failed.length} of ${members.length} files were not stored.`
|
|
1866
|
+
);
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
if ([400, 401, 403, 411, 413, 429].includes(status)) {
|
|
1870
|
+
throw new DeployError(ClientErrorCode.UploadFailed, refusalMessage(status, label), {
|
|
1871
|
+
path: first,
|
|
1872
|
+
status
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
lastError = new Error(`HTTP ${status}`);
|
|
1876
|
+
} catch (error) {
|
|
1877
|
+
if (error instanceof DeployError) throw error;
|
|
1878
|
+
if (isAbort(error)) throw cancelled();
|
|
1879
|
+
lastError = error;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
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.`;
|
|
1883
|
+
throw new DeployError(ClientErrorCode.UploadFailed, message, {
|
|
1884
|
+
path: first,
|
|
1885
|
+
cause: describeError(lastError)
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
function refusalMessage(status, label) {
|
|
1889
|
+
switch (status) {
|
|
1890
|
+
case 401:
|
|
1891
|
+
return `Upload of ${label} was refused because its upload window expired. Try deploying again.`;
|
|
1892
|
+
case 429:
|
|
1893
|
+
return `Upload of ${label} was refused: this deploy has used every upload it was given. Try deploying again.`;
|
|
1894
|
+
case 400:
|
|
1895
|
+
case 411:
|
|
1896
|
+
case 413:
|
|
1897
|
+
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.`;
|
|
1898
|
+
default:
|
|
1899
|
+
return `Upload of ${label} was refused by the upload service. Try deploying again.`;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1682
1902
|
function untyped(content) {
|
|
1683
1903
|
if (ArrayBuffer.isView(content) || content.type === "") return content;
|
|
1684
1904
|
return content.slice(0, content.size);
|
|
@@ -1689,7 +1909,8 @@ function defaultTransport(doFetch) {
|
|
|
1689
1909
|
return Xhr === void 0 ? fetchTransport(globalFetch()) : xhrTransport(Xhr);
|
|
1690
1910
|
}
|
|
1691
1911
|
function fetchTransport(doFetch) {
|
|
1692
|
-
return async (
|
|
1912
|
+
return async (request2) => {
|
|
1913
|
+
const { url, token, body, signal } = request2;
|
|
1693
1914
|
const response = await doFetch(url, {
|
|
1694
1915
|
method: "PUT",
|
|
1695
1916
|
// The view, not a copy. Wrapping it in `new Blob([body.slice().buffer])` made two extra
|
|
@@ -1704,11 +1925,11 @@ function fetchTransport(doFetch) {
|
|
|
1704
1925
|
headers: { Authorization: `Bearer ${token}` },
|
|
1705
1926
|
...signal ? { signal } : {}
|
|
1706
1927
|
});
|
|
1707
|
-
return response.status;
|
|
1928
|
+
return request2.wantsBody === true ? { status: response.status, body: await response.text() } : response.status;
|
|
1708
1929
|
};
|
|
1709
1930
|
}
|
|
1710
1931
|
function xhrTransport(Xhr) {
|
|
1711
|
-
return ({ url, token, body, signal, onSent, stallMs }) => new Promise((resolve, reject) => {
|
|
1932
|
+
return ({ url, token, body, signal, onSent, stallMs, wantsBody }) => new Promise((resolve, reject) => {
|
|
1712
1933
|
const xhr = new Xhr();
|
|
1713
1934
|
let settled = false;
|
|
1714
1935
|
let timer;
|
|
@@ -1742,7 +1963,11 @@ function xhrTransport(Xhr) {
|
|
|
1742
1963
|
onSent(event.loaded);
|
|
1743
1964
|
};
|
|
1744
1965
|
xhr.onprogress = arm;
|
|
1745
|
-
xhr.onload = () => settle(
|
|
1966
|
+
xhr.onload = () => settle(
|
|
1967
|
+
() => resolve(
|
|
1968
|
+
wantsBody === true ? { status: xhr.status, body: xhr.responseText ?? "" } : xhr.status
|
|
1969
|
+
)
|
|
1970
|
+
);
|
|
1746
1971
|
xhr.onerror = () => settle(() => reject(new TypeError("Network error during upload.")));
|
|
1747
1972
|
arm();
|
|
1748
1973
|
xhr.send(body);
|
|
@@ -1807,11 +2032,16 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
|
|
|
1807
2032
|
return;
|
|
1808
2033
|
}
|
|
1809
2034
|
const sizes = new Map(hashed.map((file) => [file.path, sizeOf(file.bytes)]));
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2035
|
+
const batches = prepared.batches ?? [];
|
|
2036
|
+
const targets = [...prepared.upload, ...batches];
|
|
2037
|
+
const totalBytes = prepared.upload.reduce((sum, target) => sum + (sizes.get(target.path) ?? 0), 0) + batches.reduce((sum, batch) => {
|
|
2038
|
+
const members = batch.files.map((ref) => ({
|
|
2039
|
+
sha256: ref.sha256,
|
|
2040
|
+
size: sizes.get(ref.path) ?? 0
|
|
2041
|
+
}));
|
|
2042
|
+
return sum + batchLayout(members).length + members.reduce((n, member) => n + member.size, 0);
|
|
2043
|
+
}, 0);
|
|
2044
|
+
if (targets.length > 0) {
|
|
1815
2045
|
onProgress({
|
|
1816
2046
|
type: "uploading",
|
|
1817
2047
|
done: prepared.reused,
|
|
@@ -1822,7 +2052,7 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
|
|
|
1822
2052
|
});
|
|
1823
2053
|
}
|
|
1824
2054
|
await uploadAll(
|
|
1825
|
-
|
|
2055
|
+
targets,
|
|
1826
2056
|
prepared.uploadUrl,
|
|
1827
2057
|
hashed,
|
|
1828
2058
|
(done, _total, bytes, path) => onProgress({
|
|
@@ -1835,7 +2065,9 @@ async function deploy(source, siteId, onProgress, signal, options = {}) {
|
|
|
1835
2065
|
...path === void 0 ? {} : { path }
|
|
1836
2066
|
}),
|
|
1837
2067
|
signal,
|
|
1838
|
-
|
|
2068
|
+
// The server's number unless the caller set one: it is tuned on the server so a deploy of many
|
|
2069
|
+
// small files speeds up without a CLI or MCP release.
|
|
2070
|
+
typeof prepared.batchConcurrency === "number" && options.upload?.batchConcurrency === void 0 ? { ...options.upload, batchConcurrency: prepared.batchConcurrency } : options.upload,
|
|
1839
2071
|
(path, attempt2, attempts, stalled) => onProgress({ type: "retrying", path, attempt: attempt2, attempts, stalled })
|
|
1840
2072
|
);
|
|
1841
2073
|
onProgress({ type: "completing", reused: prepared.reused });
|
|
@@ -2194,7 +2426,7 @@ async function signInWithCode(waitSeconds, replace = false) {
|
|
|
2194
2426
|
"Call this tool again to keep waiting. Nothing has been changed yet."
|
|
2195
2427
|
].join("\n");
|
|
2196
2428
|
}
|
|
2197
|
-
const SERVER_VERSION = "0.
|
|
2429
|
+
const SERVER_VERSION = "0.12.0";
|
|
2198
2430
|
const CREDENTIAL_WRITE = {
|
|
2199
2431
|
readOnlyHint: false,
|
|
2200
2432
|
destructiveHint: false,
|
package/package.json
CHANGED