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
- { files: manifest },
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 hashed = [];
1288
- for (const [index, file] of files.entries()) {
1289
- hashed.push({ ...file, sha256: await sha256Hex(await readForHash(file)) });
1290
- onProgress?.(index + 1, files.length);
1291
- }
1292
- return hashed;
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, targets.length, bytes, path);
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
- const file = byPath.get(target.path);
1646
- if (file === void 0) {
1647
- failed = true;
1648
- throw new DeployError(
1649
- ClientErrorCode.UploadFailed,
1650
- `The server asked for ${target.path}, which is not in the manifest.`,
1651
- { path: target.path }
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 size = sizeOf(file.bytes);
1655
- await acquire(size);
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
- signal?.throwIfAborted();
1659
- await uploadOne(target, file, {
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?.(target.path, attempt, retries + 1, stalled);
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
- failed = true;
1811
+ fail(error);
1677
1812
  throw error;
1678
1813
  } finally {
1679
- release(size);
1814
+ release(reservation);
1815
+ freeSlot(kind);
1680
1816
  }
1681
1817
  inFlight.delete(index);
1682
- done += 1;
1818
+ done += members.length;
1683
1819
  landedBytes += size;
1684
- report(file.path);
1820
+ report(members.at(-1).path);
1685
1821
  }
1686
1822
  };
1687
- const workers = Array.from({ length: Math.min(concurrency, targets.length) }, worker);
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 status = await context.put({
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 ({ url, token, body, signal }) => {
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(() => resolve2(xhr.status));
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 totalBytes = prepared.upload.reduce(
1869
- (sum, target) => sum + (sizes.get(target.path) ?? 0),
1870
- 0
1871
- );
1872
- if (prepared.upload.length > 0) {
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
- prepared.upload,
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
- options.upload,
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-CGI7Yt81.js").then(
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 };
@@ -1,5 +1,5 @@
1
1
  const name = "drop2run";
2
- const version = "0.10.0";
2
+ const version = "0.11.0";
3
3
  const description = "Publish a page, a folder of documents or a built site to Drop2Run from the command line.";
4
4
  const license = "MIT";
5
5
  const type = "module";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drop2run",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Publish a page, a folder of documents or a built site to Drop2Run from the command line.",
5
5
  "license": "MIT",
6
6
  "type": "module",