@hot-updater/cloudflare 0.30.12 → 0.31.1

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.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  import { createRequire } from "node:module";
2
- import { DEFAULT_ROLLOUT_COHORT_COUNT } from "@hot-updater/core";
3
- import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createStorageKeyBuilder, createStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
2
+ import { DEFAULT_ROLLOUT_COHORT_COUNT, getAssetBaseStorageUri, getBundlePatches, getManifestFileHash, getManifestStorageUri, stripBundleArtifactMetadata } from "@hot-updater/core";
3
+ import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createNodeStoragePlugin, createStorageKeyBuilder, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
4
4
  import Cloudflare from "cloudflare";
5
- import path from "path";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
8
9
  import { StringDecoder } from "node:string_decoder";
9
10
  import { aborted, callbackify, debuglog, inspect, promisify, stripVTControlCharacters } from "node:util";
10
11
  import process$1, { execArgv, execPath, hrtime, platform } from "node:process";
11
12
  import tty from "node:tty";
12
- import path$1 from "node:path";
13
13
  import { scheduler, setImmediate, setTimeout } from "node:timers/promises";
14
14
  import { constants } from "node:os";
15
15
  import { EventEmitter, addAbortListener, on, once, setMaxListeners } from "node:events";
@@ -353,7 +353,34 @@ function parseTargetCohorts(value) {
353
353
  }
354
354
  return null;
355
355
  }
356
- function transformRowToBundle(row) {
356
+ const parseMetadata = (value) => {
357
+ if (!value) return void 0;
358
+ if (typeof value === "string") try {
359
+ return parseMetadata(JSON.parse(value));
360
+ } catch {
361
+ return;
362
+ }
363
+ return typeof value === "object" && !Array.isArray(value) ? value : void 0;
364
+ };
365
+ const buildBundlePatchId = (bundleId, baseBundleId) => `${bundleId}:${baseBundleId}`;
366
+ const bundleToPatchRows = (bundle) => getBundlePatches(bundle).map((patch, index) => ({
367
+ id: buildBundlePatchId(bundle.id, patch.baseBundleId),
368
+ bundle_id: bundle.id,
369
+ base_bundle_id: patch.baseBundleId,
370
+ base_file_hash: patch.baseFileHash,
371
+ patch_file_hash: patch.patchFileHash,
372
+ patch_storage_uri: patch.patchStorageUri,
373
+ order_index: index
374
+ }));
375
+ function transformRowToBundle(row, patchRows = []) {
376
+ const rawMetadata = parseMetadata(row.metadata);
377
+ const patches = patchRows.slice().sort((left, right) => (left.order_index ?? 0) - (right.order_index ?? 0) || left.base_bundle_id.localeCompare(right.base_bundle_id)).map((patch) => ({
378
+ baseBundleId: patch.base_bundle_id,
379
+ baseFileHash: patch.base_file_hash,
380
+ patchFileHash: patch.patch_file_hash,
381
+ patchStorageUri: patch.patch_storage_uri
382
+ }));
383
+ const primaryPatch = patches[0] ?? null;
357
384
  return {
358
385
  id: row.id,
359
386
  channel: row.channel,
@@ -366,7 +393,15 @@ function transformRowToBundle(row) {
366
393
  targetAppVersion: row.target_app_version,
367
394
  storageUri: row.storage_uri,
368
395
  fingerprintHash: row.fingerprint_hash,
369
- metadata: row?.metadata ? JSON.parse(row?.metadata) : {},
396
+ metadata: stripBundleArtifactMetadata(rawMetadata),
397
+ manifestStorageUri: row.manifest_storage_uri ?? null,
398
+ manifestFileHash: row.manifest_file_hash ?? null,
399
+ assetBaseStorageUri: row.asset_base_storage_uri ?? null,
400
+ patches,
401
+ patchBaseBundleId: primaryPatch?.baseBundleId ?? null,
402
+ patchBaseFileHash: primaryPatch?.baseFileHash ?? null,
403
+ patchFileHash: primaryPatch?.patchFileHash ?? null,
404
+ patchStorageUri: primaryPatch?.patchStorageUri ?? null,
370
405
  rolloutCohortCount: row.rollout_cohort_count ?? DEFAULT_ROLLOUT_COHORT_COUNT,
371
406
  targetCohorts: parseTargetCohorts(row.target_cohorts)
372
407
  };
@@ -375,6 +410,27 @@ const d1Database = createDatabasePlugin({
375
410
  name: "d1Database",
376
411
  factory: (config) => {
377
412
  const cf = new Cloudflare({ apiToken: config.cloudflareApiToken });
413
+ const getPatchMap = async (bundleIds) => {
414
+ const patchMap = /* @__PURE__ */ new Map();
415
+ if (bundleIds.length === 0) return patchMap;
416
+ const sql = (0, import_lib.default)(`
417
+ SELECT *
418
+ FROM bundle_patches
419
+ WHERE bundle_id IN (${bundleIds.map(() => "?").join(", ")})
420
+ ORDER BY order_index ASC, base_bundle_id ASC
421
+ `);
422
+ const rows = await resolvePage(await cf.d1.database.query(config.databaseId, {
423
+ account_id: config.accountId,
424
+ sql,
425
+ params: bundleIds
426
+ }));
427
+ for (const row of rows) {
428
+ const current = patchMap.get(row.bundle_id) ?? [];
429
+ current.push(row);
430
+ patchMap.set(row.bundle_id, current);
431
+ }
432
+ return patchMap;
433
+ };
378
434
  async function getTotalCount(conditions) {
379
435
  const { sql: whereClause, params } = buildWhereClause(conditions);
380
436
  const countSql = (0, import_lib.default)(`SELECT COUNT(*) as total FROM bundles${whereClause}`);
@@ -394,11 +450,13 @@ const d1Database = createDatabasePlugin({
394
450
  OFFSET ?
395
451
  `);
396
452
  params.push(limit, offset);
397
- return (await resolvePage(await cf.d1.database.query(config.databaseId, {
453
+ const rows = await resolvePage(await cf.d1.database.query(config.databaseId, {
398
454
  account_id: config.accountId,
399
455
  sql,
400
456
  params
401
- }))).map(transformRowToBundle);
457
+ }));
458
+ const patchMap = await getPatchMap(rows.map((row) => row.id));
459
+ return rows.map((row) => transformRowToBundle(row, patchMap.get(row.id)));
402
460
  }
403
461
  async function queryBundlesForUpdateInfo(conditions) {
404
462
  const { sql: whereClause, params } = buildWhereClause(conditions);
@@ -406,11 +464,13 @@ const d1Database = createDatabasePlugin({
406
464
  SELECT * FROM bundles
407
465
  ${whereClause}
408
466
  `);
409
- return (await resolvePage(await cf.d1.database.query(config.databaseId, {
467
+ const rows = await resolvePage(await cf.d1.database.query(config.databaseId, {
410
468
  account_id: config.accountId,
411
469
  sql,
412
470
  params
413
- }))).map(transformRowToBundle);
471
+ }));
472
+ const patchMap = await getPatchMap(rows.map((row) => row.id));
473
+ return rows.map((row) => transformRowToBundle(row, patchMap.get(row.id)));
414
474
  }
415
475
  async function getTargetAppVersionsForUpdateInfo({ platform, channel, minBundleId }) {
416
476
  const sql = (0, import_lib.default)(`
@@ -458,13 +518,14 @@ const d1Database = createDatabasePlugin({
458
518
  async getBundleById(bundleId) {
459
519
  const sql = (0, import_lib.default)(`
460
520
  SELECT * FROM bundles WHERE id = ? LIMIT 1`);
461
- const rows = await resolvePage(await cf.d1.database.query(config.databaseId, {
521
+ const [singlePage, patchMap] = await Promise.all([cf.d1.database.query(config.databaseId, {
462
522
  account_id: config.accountId,
463
523
  sql,
464
524
  params: [bundleId]
465
- }));
525
+ }), getPatchMap([bundleId])]);
526
+ const rows = await resolvePage(singlePage);
466
527
  if (rows.length === 0) return null;
467
- return transformRowToBundle(rows[0]);
528
+ return transformRowToBundle(rows[0], patchMap.get(bundleId));
468
529
  },
469
530
  async getBundles(options) {
470
531
  const { where = {}, limit, orderBy } = options;
@@ -494,6 +555,22 @@ const d1Database = createDatabasePlugin({
494
555
  const deleteSql = (0, import_lib.default)(`
495
556
  DELETE FROM bundles WHERE id = ?
496
557
  `);
558
+ const deletePatchSql = (0, import_lib.default)(`
559
+ DELETE FROM bundle_patches WHERE bundle_id = ?
560
+ `);
561
+ await cf.d1.database.query(config.databaseId, {
562
+ account_id: config.accountId,
563
+ sql: deletePatchSql,
564
+ params: [op.data.id]
565
+ });
566
+ const deleteBasePatchSql = (0, import_lib.default)(`
567
+ DELETE FROM bundle_patches WHERE base_bundle_id = ?
568
+ `);
569
+ await cf.d1.database.query(config.databaseId, {
570
+ account_id: config.accountId,
571
+ sql: deleteBasePatchSql,
572
+ params: [op.data.id]
573
+ });
497
574
  await cf.d1.database.query(config.databaseId, {
498
575
  account_id: config.accountId,
499
576
  sql: deleteSql,
@@ -515,10 +592,13 @@ const d1Database = createDatabasePlugin({
515
592
  storage_uri,
516
593
  fingerprint_hash,
517
594
  metadata,
595
+ manifest_storage_uri,
596
+ manifest_file_hash,
597
+ asset_base_storage_uri,
518
598
  rollout_cohort_count,
519
599
  target_cohorts
520
600
  )
521
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
601
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
522
602
  `);
523
603
  const params = [
524
604
  bundle.id,
@@ -532,7 +612,10 @@ const d1Database = createDatabasePlugin({
532
612
  bundle.targetAppVersion,
533
613
  bundle.storageUri,
534
614
  bundle.fingerprintHash,
535
- bundle.metadata ? JSON.stringify(bundle.metadata) : JSON.stringify({}),
615
+ JSON.stringify(stripBundleArtifactMetadata(bundle.metadata) ?? {}),
616
+ getManifestStorageUri(bundle),
617
+ getManifestFileHash(bundle),
618
+ getAssetBaseStorageUri(bundle),
536
619
  bundle.rolloutCohortCount ?? DEFAULT_ROLLOUT_COHORT_COUNT,
537
620
  bundle.targetCohorts ? JSON.stringify(bundle.targetCohorts) : null
538
621
  ];
@@ -541,6 +624,41 @@ const d1Database = createDatabasePlugin({
541
624
  sql: upsertSql,
542
625
  params
543
626
  });
627
+ await cf.d1.database.query(config.databaseId, {
628
+ account_id: config.accountId,
629
+ sql: (0, import_lib.default)(`
630
+ DELETE FROM bundle_patches WHERE bundle_id = ?
631
+ `),
632
+ params: [bundle.id]
633
+ });
634
+ const patchRows = bundleToPatchRows(bundle);
635
+ if (patchRows.length > 0) {
636
+ const patchInsertSql = (0, import_lib.default)(`
637
+ INSERT OR REPLACE INTO bundle_patches (
638
+ id,
639
+ bundle_id,
640
+ base_bundle_id,
641
+ base_file_hash,
642
+ patch_file_hash,
643
+ patch_storage_uri,
644
+ order_index
645
+ )
646
+ VALUES (?, ?, ?, ?, ?, ?, ?)
647
+ `);
648
+ for (const patchRow of patchRows) await cf.d1.database.query(config.databaseId, {
649
+ account_id: config.accountId,
650
+ sql: patchInsertSql,
651
+ params: [
652
+ patchRow.id,
653
+ patchRow.bundle_id,
654
+ patchRow.base_bundle_id,
655
+ patchRow.base_file_hash,
656
+ patchRow.patch_file_hash,
657
+ patchRow.patch_storage_uri,
658
+ String(patchRow.order_index ?? 0)
659
+ ]
660
+ });
661
+ }
544
662
  }
545
663
  }
546
664
  };
@@ -1316,7 +1434,7 @@ const handleCommand = (filePath, rawArguments, rawOptions) => {
1316
1434
  var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1317
1435
  module.exports = isexe;
1318
1436
  isexe.sync = sync;
1319
- var fs$2 = __require("fs");
1437
+ var fs$3 = __require("fs");
1320
1438
  function checkPathExt(path, options) {
1321
1439
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
1322
1440
  if (!pathext) return true;
@@ -1333,12 +1451,12 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1333
1451
  return checkPathExt(path, options);
1334
1452
  }
1335
1453
  function isexe(path, options, cb) {
1336
- fs$2.stat(path, function(er, stat) {
1454
+ fs$3.stat(path, function(er, stat) {
1337
1455
  cb(er, er ? false : checkStat(stat, path, options));
1338
1456
  });
1339
1457
  }
1340
1458
  function sync(path, options) {
1341
- return checkStat(fs$2.statSync(path), path, options);
1459
+ return checkStat(fs$3.statSync(path), path, options);
1342
1460
  }
1343
1461
  }));
1344
1462
  //#endregion
@@ -1346,14 +1464,14 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1346
1464
  var require_mode = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1347
1465
  module.exports = isexe;
1348
1466
  isexe.sync = sync;
1349
- var fs$1 = __require("fs");
1467
+ var fs$2 = __require("fs");
1350
1468
  function isexe(path, options, cb) {
1351
- fs$1.stat(path, function(er, stat) {
1469
+ fs$2.stat(path, function(er, stat) {
1352
1470
  cb(er, er ? false : checkStat(stat, options));
1353
1471
  });
1354
1472
  }
1355
1473
  function sync(path, options) {
1356
- return checkStat(fs$1.statSync(path), options);
1474
+ return checkStat(fs$2.statSync(path), options);
1357
1475
  }
1358
1476
  function checkStat(stat, options) {
1359
1477
  return stat.isFile() && checkMode(stat, options);
@@ -1417,7 +1535,7 @@ var require_isexe = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1417
1535
  //#region ../../node_modules/.pnpm/which@2.0.2/node_modules/which/which.js
1418
1536
  var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1419
1537
  const isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
1420
- const path$4 = __require("path");
1538
+ const path$3 = __require("path");
1421
1539
  const COLON = isWindows ? ";" : ":";
1422
1540
  const isexe = require_isexe();
1423
1541
  const getNotFoundError = (cmd) => Object.assign(/* @__PURE__ */ new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -1447,7 +1565,7 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1447
1565
  if (i === pathEnv.length) return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
1448
1566
  const ppRaw = pathEnv[i];
1449
1567
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
1450
- const pCmd = path$4.join(pathPart, cmd);
1568
+ const pCmd = path$3.join(pathPart, cmd);
1451
1569
  resolve(subStep(!pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd, i, 0));
1452
1570
  });
1453
1571
  const subStep = (p, i, ii) => new Promise((resolve, reject) => {
@@ -1468,7 +1586,7 @@ var require_which = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1468
1586
  for (let i = 0; i < pathEnv.length; i++) {
1469
1587
  const ppRaw = pathEnv[i];
1470
1588
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
1471
- const pCmd = path$4.join(pathPart, cmd);
1589
+ const pCmd = path$3.join(pathPart, cmd);
1472
1590
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
1473
1591
  for (let j = 0; j < pathExt.length; j++) {
1474
1592
  const cur = p + pathExt[j];
@@ -1499,7 +1617,7 @@ var require_path_key = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1499
1617
  //#endregion
1500
1618
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/resolveCommand.js
1501
1619
  var require_resolveCommand = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1502
- const path$3 = __require("path");
1620
+ const path$2 = __require("path");
1503
1621
  const which = require_which();
1504
1622
  const getPathKey = require_path_key();
1505
1623
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -1514,12 +1632,12 @@ var require_resolveCommand = /* @__PURE__ */ __commonJSMin(((exports, module) =>
1514
1632
  try {
1515
1633
  resolved = which.sync(parsed.command, {
1516
1634
  path: env[getPathKey({ env })],
1517
- pathExt: withoutPathExt ? path$3.delimiter : void 0
1635
+ pathExt: withoutPathExt ? path$2.delimiter : void 0
1518
1636
  });
1519
1637
  } catch (e) {} finally {
1520
1638
  if (shouldSwitchCwd) process.chdir(cwd);
1521
1639
  }
1522
- if (resolved) resolved = path$3.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
1640
+ if (resolved) resolved = path$2.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
1523
1641
  return resolved;
1524
1642
  }
1525
1643
  function resolveCommand(parsed) {
@@ -1568,16 +1686,16 @@ var require_shebang_command = /* @__PURE__ */ __commonJSMin(((exports, module) =
1568
1686
  //#endregion
1569
1687
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
1570
1688
  var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1571
- const fs = __require("fs");
1689
+ const fs$1 = __require("fs");
1572
1690
  const shebangCommand = require_shebang_command();
1573
1691
  function readShebang(command) {
1574
1692
  const size = 150;
1575
1693
  const buffer = Buffer.alloc(size);
1576
1694
  let fd;
1577
1695
  try {
1578
- fd = fs.openSync(command, "r");
1579
- fs.readSync(fd, buffer, 0, size, 0);
1580
- fs.closeSync(fd);
1696
+ fd = fs$1.openSync(command, "r");
1697
+ fs$1.readSync(fd, buffer, 0, size, 0);
1698
+ fs$1.closeSync(fd);
1581
1699
  } catch (e) {}
1582
1700
  return shebangCommand(buffer.toString());
1583
1701
  }
@@ -1586,7 +1704,7 @@ var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1586
1704
  //#endregion
1587
1705
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/parse.js
1588
1706
  var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1589
- const path$2 = __require("path");
1707
+ const path$1 = __require("path");
1590
1708
  const resolveCommand = require_resolveCommand();
1591
1709
  const escape = require_escape();
1592
1710
  const readShebang = require_readShebang();
@@ -1609,7 +1727,7 @@ var require_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
1609
1727
  const needsShell = !isExecutableRegExp.test(commandFile);
1610
1728
  if (parsed.options.forceShell || needsShell) {
1611
1729
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
1612
- parsed.command = path$2.normalize(parsed.command);
1730
+ parsed.command = path$1.normalize(parsed.command);
1613
1731
  parsed.command = escape.command(parsed.command);
1614
1732
  parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
1615
1733
  parsed.args = [
@@ -1718,33 +1836,33 @@ function toPath(urlOrPath) {
1718
1836
  }
1719
1837
  function traversePathUp(startPath) {
1720
1838
  return { *[Symbol.iterator]() {
1721
- let currentPath = path$1.resolve(toPath(startPath));
1839
+ let currentPath = path.resolve(toPath(startPath));
1722
1840
  let previousPath;
1723
1841
  while (previousPath !== currentPath) {
1724
1842
  yield currentPath;
1725
1843
  previousPath = currentPath;
1726
- currentPath = path$1.resolve(currentPath, "..");
1844
+ currentPath = path.resolve(currentPath, "..");
1727
1845
  }
1728
1846
  } };
1729
1847
  }
1730
1848
  //#endregion
1731
1849
  //#region ../../node_modules/.pnpm/npm-run-path@6.0.0/node_modules/npm-run-path/index.js
1732
1850
  const npmRunPath = ({ cwd = process$1.cwd(), path: pathOption = process$1.env[pathKey()], preferLocal = true, execPath = process$1.execPath, addExecPath = true } = {}) => {
1733
- const cwdPath = path$1.resolve(toPath(cwd));
1851
+ const cwdPath = path.resolve(toPath(cwd));
1734
1852
  const result = [];
1735
- const pathParts = pathOption.split(path$1.delimiter);
1853
+ const pathParts = pathOption.split(path.delimiter);
1736
1854
  if (preferLocal) applyPreferLocal(result, pathParts, cwdPath);
1737
1855
  if (addExecPath) applyExecPath(result, pathParts, execPath, cwdPath);
1738
- return pathOption === "" || pathOption === path$1.delimiter ? `${result.join(path$1.delimiter)}${pathOption}` : [...result, pathOption].join(path$1.delimiter);
1856
+ return pathOption === "" || pathOption === path.delimiter ? `${result.join(path.delimiter)}${pathOption}` : [...result, pathOption].join(path.delimiter);
1739
1857
  };
1740
1858
  const applyPreferLocal = (result, pathParts, cwdPath) => {
1741
1859
  for (const directory of traversePathUp(cwdPath)) {
1742
- const pathPart = path$1.join(directory, "node_modules/.bin");
1860
+ const pathPart = path.join(directory, "node_modules/.bin");
1743
1861
  if (!pathParts.includes(pathPart)) result.push(pathPart);
1744
1862
  }
1745
1863
  };
1746
1864
  const applyExecPath = (result, pathParts, execPath, cwdPath) => {
1747
- const pathPart = path$1.resolve(cwdPath, toPath(execPath), "..");
1865
+ const pathPart = path.resolve(cwdPath, toPath(execPath), "..");
1748
1866
  if (!pathParts.includes(pathPart)) result.push(pathPart);
1749
1867
  };
1750
1868
  const npmRunPathEnv = ({ env = process$1.env, ...options } = {}) => {
@@ -2754,7 +2872,7 @@ const mapNode = ({ options }) => {
2754
2872
  const handleNodeOption = (file, commandArguments, { node: shouldHandleNode = false, nodePath = execPath, nodeOptions = execArgv.filter((nodeOption) => !nodeOption.startsWith("--inspect")), cwd, execPath: formerNodePath, ...options }) => {
2755
2873
  if (formerNodePath !== void 0) throw new TypeError("The \"execPath\" option has been removed. Please use the \"nodePath\" option instead.");
2756
2874
  const normalizedNodePath = safeNormalizeFileUrl(nodePath, "The \"nodePath\" option");
2757
- const resolvedNodePath = path$1.resolve(cwd, normalizedNodePath);
2875
+ const resolvedNodePath = path.resolve(cwd, normalizedNodePath);
2758
2876
  const newOptions = {
2759
2877
  ...options,
2760
2878
  nodePath: resolvedNodePath,
@@ -2766,7 +2884,7 @@ const handleNodeOption = (file, commandArguments, { node: shouldHandleNode = fal
2766
2884
  commandArguments,
2767
2885
  newOptions
2768
2886
  ];
2769
- if (path$1.basename(file, ".exe") === "node") throw new TypeError("When the \"node\" option is true, the first argument does not need to be \"node\".");
2887
+ if (path.basename(file, ".exe") === "node") throw new TypeError("When the \"node\" option is true, the first argument does not need to be \"node\".");
2770
2888
  return [
2771
2889
  resolvedNodePath,
2772
2890
  [
@@ -2850,7 +2968,7 @@ const serializeEncoding = (encoding) => typeof encoding === "string" ? `"${encod
2850
2968
  //#region ../../node_modules/.pnpm/execa@9.5.2/node_modules/execa/lib/arguments/cwd.js
2851
2969
  const normalizeCwd = (cwd = getDefaultCwd()) => {
2852
2970
  const cwdString = safeNormalizeFileUrl(cwd, "The \"cwd\" option");
2853
- return path$1.resolve(cwdString);
2971
+ return path.resolve(cwdString);
2854
2972
  };
2855
2973
  const getDefaultCwd = () => {
2856
2974
  try {
@@ -2888,7 +3006,7 @@ const normalizeOptions = (filePath, rawArguments, rawOptions) => {
2888
3006
  options.killSignal = normalizeKillSignal(options.killSignal);
2889
3007
  options.forceKillAfterDelay = normalizeForceKillAfterDelay(options.forceKillAfterDelay);
2890
3008
  options.lines = options.lines.map((lines, fdNumber) => lines && !BINARY_ENCODINGS.has(options.encoding) && options.buffer[fdNumber]);
2891
- if (process$1.platform === "win32" && path$1.basename(file, ".exe") === "cmd") commandArguments.unshift("/q");
3009
+ if (process$1.platform === "win32" && path.basename(file, ".exe") === "cmd") commandArguments.unshift("/q");
2892
3010
  return {
2893
3011
  file,
2894
3012
  commandArguments,
@@ -6723,28 +6841,8 @@ const createWrangler = ({ stdio, accountId, cloudflareApiToken, cwd }) => {
6723
6841
  //#region src/r2Storage.ts
6724
6842
  /**
6725
6843
  * Cloudflare R2 storage plugin for Hot Updater.
6726
- *
6727
- * Note: This plugin does not support `getDownloadUrl()`.
6728
- * If you need download URL generation, use `s3Storage` from `@hot-updater/aws` instead,
6729
- * which is fully compatible with Cloudflare R2.
6730
- *
6731
- * @example
6732
- * ```typescript
6733
- * // Using s3Storage with Cloudflare R2 for download URL support
6734
- * import { s3Storage } from "@hot-updater/aws";
6735
- *
6736
- * s3Storage({
6737
- * region: "auto",
6738
- * endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
6739
- * credentials: {
6740
- * accessKeyId: "YOUR_ACCESS_KEY_ID",
6741
- * secretAccessKey: "YOUR_SECRET_ACCESS_KEY",
6742
- * },
6743
- * bucketName: "YOUR_BUCKET_NAME",
6744
- * })
6745
- * ```
6746
6844
  */
6747
- const r2Storage = createStoragePlugin({
6845
+ const r2Storage = createNodeStoragePlugin({
6748
6846
  name: "r2Storage",
6749
6847
  supportedProtocol: "r2",
6750
6848
  factory: (config) => {
@@ -6777,8 +6875,17 @@ const r2Storage = createStoragePlugin({
6777
6875
  }
6778
6876
  return { storageUri: `r2://${bucketName}/${Key}` };
6779
6877
  },
6780
- async getDownloadUrl() {
6781
- throw new Error("`r2Storage` does not support `getDownloadUrl()`. Use `s3Storage` from `@hot-updater/aws` instead (compatible with Cloudflare R2).\n\nExample:\ns3Storage({\n region: 'auto',\n endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com',\n credentials: {\n accessKeyId: 'YOUR_ACCESS_KEY_ID',\n secretAccessKey: 'YOUR_SECRET_ACCESS_KEY',\n },\n bucketName: 'YOUR_BUCKET_NAME',\n})");
6878
+ async downloadFile(storageUri, filePath) {
6879
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
6880
+ if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
6881
+ try {
6882
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
6883
+ const { stderr, exitCode } = await wrangler("r2", "object", "get", [bucketName, key].join("/"), "--file", filePath, "--remote");
6884
+ if (exitCode !== 0 && stderr) throw new Error(stderr);
6885
+ } catch (error) {
6886
+ if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
6887
+ throw error;
6888
+ }
6782
6889
  }
6783
6890
  };
6784
6891
  }