permaweb-deploy 3.4.3 → 3.4.5

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.
Files changed (40) hide show
  1. package/README.md +18 -14
  2. package/bin/run.js +0 -0
  3. package/dist/chunks/display-CyJXNtkl.js +53 -0
  4. package/dist/chunks/display-CyJXNtkl.js.map +1 -0
  5. package/dist/chunks/{upload-workflow-zlELdPNp.js → upload-workflow-CcYev67_.js} +204 -21
  6. package/dist/chunks/upload-workflow-CcYev67_.js.map +1 -0
  7. package/dist/chunks/{uploader-DDS_d-O_.js → uploader-CIHu22Fw.js} +5 -1
  8. package/dist/chunks/uploader-CIHu22Fw.js.map +1 -0
  9. package/dist/commands/deploy.js +87 -82
  10. package/dist/commands/deploy.js.map +1 -1
  11. package/dist/commands/upload.js +35 -50
  12. package/dist/commands/upload.js.map +1 -1
  13. package/dist/constants/flags.js +9 -0
  14. package/dist/constants/flags.js.map +1 -1
  15. package/dist/src/commands/deploy.d.ts.map +1 -1
  16. package/dist/src/commands/upload.d.ts.map +1 -1
  17. package/dist/src/constants/flags.d.ts +5 -1
  18. package/dist/src/constants/flags.d.ts.map +1 -1
  19. package/dist/src/types/index.d.ts +1 -1
  20. package/dist/src/types/index.d.ts.map +1 -1
  21. package/dist/src/utils/__tests__/display.test.d.ts +2 -0
  22. package/dist/src/utils/__tests__/display.test.d.ts.map +1 -0
  23. package/dist/src/utils/chalk.d.ts +9 -0
  24. package/dist/src/utils/chalk.d.ts.map +1 -0
  25. package/dist/src/utils/display.d.ts +7 -0
  26. package/dist/src/utils/display.d.ts.map +1 -0
  27. package/dist/src/utils/hyperbeam-uploader.d.ts +36 -7
  28. package/dist/src/utils/hyperbeam-uploader.d.ts.map +1 -1
  29. package/dist/src/utils/uploader.d.ts +3 -1
  30. package/dist/src/utils/uploader.d.ts.map +1 -1
  31. package/dist/src/workflows/upload-workflow.d.ts +7 -1
  32. package/dist/src/workflows/upload-workflow.d.ts.map +1 -1
  33. package/dist/tests/setup.d.ts +1 -1
  34. package/dist/tests/setup.d.ts.map +1 -1
  35. package/dist/utils/uploader.js +1 -1
  36. package/dist/workflows/upload-workflow.js +2 -3
  37. package/dist/workflows/upload-workflow.js.map +1 -1
  38. package/package.json +26 -28
  39. package/dist/chunks/upload-workflow-zlELdPNp.js.map +0 -1
  40. package/dist/chunks/uploader-DDS_d-O_.js.map +0 -1
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # Permaweb Deploy
2
2
 
3
- Inspired by the [cookbook github action deployment guide](https://cookbook.arweave.dev/guides/deployment/github-action.html), `permaweb-deploy` is a Node.js command-line tool designed to streamline the deployment of web applications to the permaweb using Arweave. It uploads your build folder or a single file, creates Arweave manifests, and updates ArNS (Arweave Name Service) records via ANT (Arweave Name Token) with the transaction ID.
3
+ Inspired by the [cookbook github action deployment guide](https://cookbook.arweave.dev/guides/deployment/github-action.html), `permaweb-deploy` is a Node.js command-line tool designed to streamline the deployment of web applications to the permaweb using Arweave. It uploads your build folder or a single file, creates Arweave manifests, and can optionally update ArNS (Arweave Name Service) records via ANT (Arweave Name Token) with the transaction ID.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Turbo SDK Integration:** Uses Turbo SDK for fast, reliable file uploads to Arweave
8
8
  - **On-Demand Payment:** Pay with ARIO or Base-ETH tokens on-demand during upload
9
9
  - **Arweave Manifest v0.2.0:** Creates manifests with fallback support for SPAs
10
- - **ArNS Updates:** Updates ArNS records via ANT with new transaction IDs and metadata
10
+ - **Optional ArNS Updates:** Updates ArNS records via ANT with new transaction IDs and metadata
11
11
  - **Automated Workflow:** Integrates with GitHub Actions for continuous deployment
12
12
  - **Git Hash Tagging:** Automatically tags deployments with Git commit hashes
13
13
  - **404 Fallback Detection:** Automatically detects and sets 404.html as fallback
@@ -77,7 +77,7 @@ Run the deploy command without arguments to be guided through all deployment opt
77
77
  permaweb-deploy deploy
78
78
  ```
79
79
 
80
- This will prompt you for:
80
+ This uploads to the permaweb by default. Use `--use-arns` or `--arns-name` to run the guided ArNS update flow, which will prompt you for:
81
81
 
82
82
  - ArNS name
83
83
  - Wallet method (file, string, or environment variable)
@@ -91,7 +91,10 @@ Use flags for faster, scriptable deployments:
91
91
 
92
92
  ```bash
93
93
  # Basic deployment with wallet file
94
- permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json
94
+ permaweb-deploy deploy --wallet ./wallet.json
95
+
96
+ # Deployment with ArNS update
97
+ permaweb-deploy deploy --use-arns --arns-name my-app --wallet ./wallet.json
95
98
  ```
96
99
 
97
100
  Deploy using private key directly:
@@ -118,11 +121,12 @@ Deploy a single file:
118
121
  permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --deploy-file ./path/to/file.txt
119
122
  ```
120
123
 
121
- ### Upload only (no ArNS)
124
+ ### Upload/deploy without ArNS
122
125
 
123
- To upload a folder or file to Arweave **without** updating an ArNS name, use the `upload` command (same Turbo upload, dedupe cache, and payment options as deploy, minus ArNS flags):
126
+ `deploy` uploads without updating ArNS by default. You can also use the `upload` command explicitly for the same Turbo upload, dedupe cache, and payment options as deploy, minus ArNS flags:
124
127
 
125
128
  ```bash
129
+ permaweb-deploy deploy --wallet ./wallet.json --deploy-folder ./dist
126
130
  permaweb-deploy upload --wallet ./wallet.json --deploy-folder ./dist
127
131
  permaweb-deploy upload --wallet ./wallet.json --deploy-file ./dist/index.html
128
132
  DEPLOY_KEY=$(base64 -i wallet.json) permaweb-deploy upload --deploy-folder ./dist
@@ -217,14 +221,13 @@ permaweb-deploy upload \
217
221
  --uploader https://hyperbeam.example.com
218
222
 
219
223
  permaweb-deploy deploy \
220
- --arns-name my-app \
221
224
  --wallet ./wallet.json \
222
225
  --deploy-folder ./dist \
223
226
  --uploader-type hyperbeam \
224
227
  --uploader https://hyperbeam.example.com
225
228
  ```
226
229
 
227
- If the node follows the standard AO-paid HyperBEAM bundler flow, the CLI can fund the uploader wallet before uploading:
230
+ If the node follows the standard AO-paid HyperBEAM bundler flow, either command can fund the uploader wallet before uploading:
228
231
 
229
232
  ```bash
230
233
  permaweb-deploy upload \
@@ -238,15 +241,16 @@ permaweb-deploy upload \
238
241
  **Notes:**
239
242
 
240
243
  - Turbo billing and signer behavior follow Turbo.
241
- - HyperBEAM uploads require an Arweave JWK signer. With `--hyperbeam-auto-fund`, the CLI signs each data item, asks the node's `metering@1.0` device for a byte quote, sends AO to the node address from `/~meta@1.0/info/address`, imports that deposit through `/~ao-payment@1.0/ingest`, and waits for the uploader's balance at `/ledger~node-process@1.0/now/balance/<address>` before uploading. The default route is `/~bundler@1.0/item?codec-device=ans104@1.0`; override it with `--hyperbeam-upload-path` if your node exposes a different bundler route.
242
- - `--hyperbeam-fund-amount` is an optional override for the minimum local ledger balance to ensure, in AO base units. Without it, `--hyperbeam-auto-fund` uses the node's `metering@1.0` quote for the signed byte count. Use `--hyperbeam-token-id` only for a non-default AO token process, and `--hyperbeam-ledger-id` only for a non-default local ledger profile.
244
+ - HyperBEAM uploads require an Arweave JWK signer. Before uploading, the CLI checks the node address from `/~meta@1.0/info/address` against `https://arweave.net/wallet/<address>/balance` and aborts if the bundler wallet has 0 AR. With `--hyperbeam-auto-fund`, the CLI signs each data item, asks the node's byte-pricing profile for a quote, sends AO to the node deposit address, imports that deposit through `/~ao-payment@1.0/ingest`, and waits for the uploader's balance at `/ledger~node-process@1.0/now/balance/<address>` before uploading. The default route is `/~bundler@1.0/item?codec-device=ans104@1.0`; override it with `--hyperbeam-upload-path` if your node exposes a different bundler route.
245
+ - `--hyperbeam-fund-amount` is an optional override for the minimum local ledger balance to ensure, in AO base units. Without it, `--hyperbeam-auto-fund` uses the node's byte-pricing quote for the signed byte count. Use `--hyperbeam-token-id` only for a non-default AO token process, and `--hyperbeam-ledger-id` only for a non-default local ledger profile.
243
246
  - Use a **base URL only** (e.g. `https://up.arweave.net` or `https://hyperbeam.example.com`), not a path to a specific file or route.
244
247
 
245
248
  ### Command Options
246
249
 
247
- **`deploy`** (ArNS update):
250
+ **`deploy`** (upload by default, optional ArNS update):
248
251
 
249
- - `--arns-name, -n` (required): The ArNS name to update
252
+ - `--use-arns`: Update an ArNS/ANT record after upload
253
+ - `--arns-name, -n`: The ArNS name to update. Required when using `--use-arns`; also implies ArNS mode for backwards compatibility.
250
254
  - `--ario-process, -p`: ARIO process to use (`mainnet`, `testnet`, or a custom process ID). Default: `mainnet`
251
255
  - `--deploy-folder, -d`: Folder to deploy. Default: `./dist`
252
256
  - `--deploy-file, -f`: Deploy a single file instead of a folder
@@ -268,7 +272,7 @@ permaweb-deploy upload \
268
272
  - `--hyperbeam-ledger-id`: Advanced local HyperBEAM ledger ID override
269
273
  - `--hyperbeam-ao-state-url`: AO state endpoint used while waiting for auto-fund transfer assignment. Default: `https://state.forward.computer`
270
274
 
271
- **`upload`** (no ArNS): accepts `--deploy-folder`, `--deploy-file`, wallet/signer flags, uploader flags, `--on-demand` / `--max-token-amount`, and dedupe flags only.
275
+ **`upload`** (explicit upload without ArNS): accepts `--deploy-folder`, `--deploy-file`, wallet/signer flags, uploader flags, `--on-demand` / `--max-token-amount`, and dedupe flags only.
272
276
 
273
277
  ### Deduplication
274
278
 
@@ -617,7 +621,7 @@ permaweb-deploy/
617
621
 
618
622
  - **Dedicated Wallet:** Always use a dedicated wallet for deployments to minimize security risks
619
623
  - **Wallet Encoding:** Arweave wallets must be base64 encoded to be used in the deployment script
620
- - **ArNS Name:** The ArNS Name must be passed so that the ANT Process can be resolved to update the target undername or root record
624
+ - **ArNS Name:** Required only when updating an ANT/ArNS target undername or root record
621
625
  - **Turbo Credits:** Ensure your wallet has sufficient Turbo Credits, or use on-demand payment for automatic funding
622
626
  - **On-Demand Limits:** Set reasonable `--max-token-amount` limits to prevent unexpected costs
623
627
  - **Secret Management:** Keep your `DEPLOY_KEY` secret secure and never commit it to your repository
package/bin/run.js CHANGED
File without changes
@@ -0,0 +1,53 @@
1
+ import { c as chalk } from './upload-workflow-CcYev67_.js';
2
+
3
+ const AO_BASE_UNITS = 1000000000000n;
4
+ function formatUploadSize(size) {
5
+ return `${(size.signedBytes ?? size.payloadBytes).toLocaleString()} bytes`;
6
+ }
7
+ function formatUploadCost(cost) {
8
+ if (cost.token !== "AO") {
9
+ return `${cost.amount.toString()}`;
10
+ }
11
+ const whole = cost.amount / AO_BASE_UNITS;
12
+ const fraction = cost.amount % AO_BASE_UNITS;
13
+ const decimal = fraction === 0n ? whole.toString() : `${whole.toString()}.${fraction.toString().padStart(12, "0").replaceAll(/0+$/g, "")}`;
14
+ return `${decimal} AO`;
15
+ }
16
+ function formatDisplayRows(rows) {
17
+ return rows.map(([label, value]) => `${label}: ${value}`).join("\n");
18
+ }
19
+ function fundingDisplay(section) {
20
+ const fundingLine = section.split("\n").map((line) => line.trim()).find((line) => line.startsWith("- "))?.replace(/^- /, "");
21
+ if (!fundingLine) {
22
+ return section;
23
+ }
24
+ return fundingLine.replace(/^AO: send funds to /, "Sending AO to ").replace(/\. Local ledger:.*$/, "");
25
+ }
26
+ function formatUploadError(message, title = "Upload failed") {
27
+ const rows = [];
28
+ const sections = message.split(/\n{2,}/).map((section) => section.trim()).filter(Boolean);
29
+ for (const [index, section] of sections.entries()) {
30
+ if (index === 0) {
31
+ rows.push(["Error", chalk.red(section)]);
32
+ continue;
33
+ }
34
+ if (section.startsWith("Required upload credit:")) {
35
+ rows.push([
36
+ "Required upload credit",
37
+ chalk.blue(section.replace(/^Required upload credit:\s*/, ""))
38
+ ]);
39
+ continue;
40
+ }
41
+ if (section.startsWith("The HyperBEAM node requires AO")) {
42
+ rows.push(["Funding", fundingDisplay(section)]);
43
+ continue;
44
+ }
45
+ rows.push(["Note", section]);
46
+ }
47
+ return `${chalk.bold(chalk.red(title))}
48
+
49
+ ${formatDisplayRows(rows)}`;
50
+ }
51
+
52
+ export { formatUploadError as a, formatUploadSize as b, formatUploadCost as c, formatDisplayRows as f };
53
+ //# sourceMappingURL=display-CyJXNtkl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display-CyJXNtkl.js","sources":["../../src/utils/display.ts"],"sourcesContent":["import { chalk } from './chalk.js'\nimport type { UploadCost, UploadSize } from './hyperbeam-uploader.js'\n\nconst AO_BASE_UNITS = 1_000_000_000_000n\n\nexport type DisplayRow = [label: string, value: string]\n\nexport function formatUploadSize(size: UploadSize): string {\n return `${(size.signedBytes ?? size.payloadBytes).toLocaleString()} bytes`\n}\n\nexport function formatUploadCost(cost: UploadCost): string {\n if (cost.token !== 'AO') {\n return `${cost.amount.toString()}`\n }\n\n const whole = cost.amount / AO_BASE_UNITS\n const fraction = cost.amount % AO_BASE_UNITS\n const decimal =\n fraction === 0n\n ? whole.toString()\n : `${whole.toString()}.${fraction.toString().padStart(12, '0').replaceAll(/0+$/g, '')}`\n\n return `${decimal} AO`\n}\n\nexport function formatDisplayRows(rows: DisplayRow[]): string {\n return rows.map(([label, value]) => `${label}: ${value}`).join('\\n')\n}\n\nfunction fundingDisplay(section: string): string {\n const fundingLine = section\n .split('\\n')\n .map((line) => line.trim())\n .find((line) => line.startsWith('- '))\n ?.replace(/^- /, '')\n\n if (!fundingLine) {\n return section\n }\n\n return fundingLine\n .replace(/^AO: send funds to /, 'Sending AO to ')\n .replace(/\\. Local ledger:.*$/, '')\n}\n\nexport function formatUploadError(message: string, title = 'Upload failed'): string {\n const rows: DisplayRow[] = []\n const sections = message\n .split(/\\n{2,}/)\n .map((section) => section.trim())\n .filter(Boolean)\n\n for (const [index, section] of sections.entries()) {\n if (index === 0) {\n rows.push(['Error', chalk.red(section)])\n continue\n }\n\n if (section.startsWith('Required upload credit:')) {\n rows.push([\n 'Required upload credit',\n chalk.blue(section.replace(/^Required upload credit:\\s*/, '')),\n ])\n continue\n }\n\n if (section.startsWith('The HyperBEAM node requires AO')) {\n rows.push(['Funding', fundingDisplay(section)])\n continue\n }\n\n rows.push(['Note', section])\n }\n\n return `${chalk.bold(chalk.red(title))}\\n\\n${formatDisplayRows(rows)}`\n}\n"],"names":[],"mappings":"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"}
@@ -1,9 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { TurboFactory, ETHToTokenAmount, ARIOToTokenAmount, OnDemandFunding } from '@ardrive/turbo-sdk';
4
- import chalk from 'chalk';
5
4
  import ora from 'ora';
6
- import { l as loadCache, u as uploadFile, c as cleanupCache, s as saveCache, a as uploadFolder } from './uploader-DDS_d-O_.js';
5
+ import { l as loadCache, u as uploadFile, c as cleanupCache, s as saveCache, a as uploadFolder } from './uploader-CIHu22Fw.js';
7
6
  import { createHash } from 'node:crypto';
8
7
  import { createRequire } from 'node:module';
9
8
  import { Readable } from 'node:stream';
@@ -11,6 +10,53 @@ import { createDataItemSigner, message } from '@permaweb/aoconnect';
11
10
  import { expandPath } from '../utils/path.js';
12
11
  import { createSigner } from '../utils/signer.js';
13
12
 
13
+ const RESET = "\x1B[0m";
14
+ const WHITE = "\x1B[37m";
15
+ const BG_BLACK = "\x1B[40m";
16
+ const colorCodes = [
17
+ ["bold", "\x1B[1m"],
18
+ ["dim", "\x1B[2m"],
19
+ ["italic", "\x1B[3m"],
20
+ ["underline", "\x1B[4m"],
21
+ ["inverse", "\x1B[7m"],
22
+ ["strikethrough", "\x1B[9m"],
23
+ ["black", "\x1B[30m"],
24
+ ["red", "\x1B[31m"],
25
+ ["green", "\x1B[32m"],
26
+ ["yellow", "\x1B[33m"],
27
+ ["blue", "\x1B[34m"],
28
+ ["magenta", "\x1B[35m"],
29
+ ["cyan", "\x1B[36m"],
30
+ ["white", WHITE],
31
+ ["gray", "\x1B[90m"],
32
+ ["grey", "\x1B[90m"],
33
+ ["blackBright", "\x1B[90m"],
34
+ ["redBright", "\x1B[91m"],
35
+ ["greenBright", "\x1B[92m"],
36
+ ["yellowBright", "\x1B[93m"],
37
+ ["blueBright", "\x1B[94m"],
38
+ ["magentaBright", "\x1B[95m"],
39
+ ["cyanBright", "\x1B[96m"],
40
+ ["whiteBright", "\x1B[97m"],
41
+ ["bgBlack", BG_BLACK],
42
+ ["bgRed", "\x1B[41m"],
43
+ ["bgGreen", "\x1B[42m"],
44
+ ["bgYellow", "\x1B[43m"],
45
+ ["bgBlue", "\x1B[44m"],
46
+ ["bgMagenta", "\x1B[45m"],
47
+ ["bgCyan", "\x1B[46m"],
48
+ ["bgWhite", "\x1B[47m"],
49
+ ["bgBlackBright", "\x1B[100m"]
50
+ ];
51
+ const createColorFunction = (code) => (text) => `${code}${String(text)}${RESET}`;
52
+ const colorFunctions = {};
53
+ for (const [name, code] of colorCodes) {
54
+ colorFunctions[name] = createColorFunction(code);
55
+ }
56
+ const chalk = Object.assign(colorFunctions, {
57
+ highlight: (text) => `${BG_BLACK}${WHITE}${String(text)} ${RESET}`
58
+ });
59
+
14
60
  class HyperbalanceError extends Error {
15
61
  cause;
16
62
  constructor(message, cause) {
@@ -548,6 +594,8 @@ async function fetchHyperbeamOperatorAddress(fetcher, nodeUrl) {
548
594
 
549
595
  const require$1 = createRequire(import.meta.url);
550
596
  const { ArweaveSigner, DataItem, createData } = require$1("@dha-team/arbundles");
597
+ const AO_BASE_UNITS = 1000000000000n;
598
+ const ARWEAVE_GATEWAY = "https://arweave.net";
551
599
  async function readableToBuffer(stream) {
552
600
  const chunks = [];
553
601
  for await (const chunk of stream) {
@@ -593,6 +641,24 @@ function parseHyperbeamFundAmount(value) {
593
641
  }
594
642
  return BigInt(value);
595
643
  }
644
+ function formatAoAmount(amount) {
645
+ const whole = amount / AO_BASE_UNITS;
646
+ const fraction = amount % AO_BASE_UNITS;
647
+ if (fraction === 0n) {
648
+ return `${whole.toString()} AO`;
649
+ }
650
+ return `${whole.toString()}.${fraction.toString().padStart(12, "0").replaceAll(/0+$/g, "")} AO`;
651
+ }
652
+ function responsePreview(body) {
653
+ const preview = body.replaceAll(/\s+/g, " ").trim();
654
+ if (!preview) {
655
+ return void 0;
656
+ }
657
+ if (/^(<!doctype html\b|<html\b)/i.test(preview)) {
658
+ return "HTML error response";
659
+ }
660
+ return preview.slice(0, 300);
661
+ }
596
662
  async function ensureHyperbeamCredit(options, profile) {
597
663
  const jwk = JSON.parse(Buffer.from(options.deployKey, "base64").toString("utf8"));
598
664
  const recipient = arweaveAddressFromJwk(jwk);
@@ -663,9 +729,46 @@ async function autoFundQuotedHyperbeamLedger(options) {
663
729
  profile
664
730
  );
665
731
  }
666
- function hyperbeamBundlerLink(uploader, id) {
732
+ async function quoteHyperbeamUpload(options) {
733
+ const profile = await discoverHyperbeamAoBundlerProfile({
734
+ ledgerId: options.ledgerId,
735
+ nodeUrl: options.uploader,
736
+ tokenId: options.tokenId
737
+ });
738
+ const quote = await new HyperbalanceClient({ nodeUrl: options.uploader }).quoteAuto({
739
+ action: options.quoteAction ?? "hyperbeam-upload",
740
+ params: { bytes: options.signedBytes },
741
+ profile
742
+ });
743
+ return { amount: quote.amount, ledgerId: quote.ledgerId, tokenId: quote.tokenId };
744
+ }
745
+ function hyperbeamBundlerLink(uploader, id, isManifest = false) {
667
746
  const normalizedBase = uploader.endsWith("/") ? uploader : `${uploader}/`;
668
- return new URL(`~arweave@2.9/raw=${encodeURIComponent(id)}`, normalizedBase).toString();
747
+ return new URL(`${encodeURIComponent(id)}${isManifest ? "/" : ""}`, normalizedBase).toString();
748
+ }
749
+ async function preflightHyperbeamBundlerArBalance(uploader) {
750
+ const nodeUrl = uploader.replace(/\/+$/, "");
751
+ const addressRes = await fetch(`${nodeUrl}/~meta@1.0/info/address`);
752
+ if (!addressRes.ok) {
753
+ throw new Error(`HyperBEAM bundler address check failed with HTTP ${addressRes.status}`);
754
+ }
755
+ const address = (await addressRes.text()).trim();
756
+ if (!address) {
757
+ throw new Error("HyperBEAM bundler address check returned an empty address");
758
+ }
759
+ const balanceRes = await fetch(`${ARWEAVE_GATEWAY}/wallet/${encodeURIComponent(address)}/balance`);
760
+ if (!balanceRes.ok) {
761
+ throw new Error(`HyperBEAM bundler AR balance check failed with HTTP ${balanceRes.status}`);
762
+ }
763
+ const balance = (await balanceRes.text()).trim();
764
+ if (!/^\d+$/.test(balance)) {
765
+ throw new Error("HyperBEAM bundler AR balance check returned an invalid balance");
766
+ }
767
+ if (BigInt(balance) === 0n) {
768
+ throw new Error(
769
+ `HyperBEAM bundler wallet ${address} has 0 AR; upload aborted because the node cannot seed data to Arweave.`
770
+ );
771
+ }
669
772
  }
670
773
  function responseId(headers, body) {
671
774
  const headerId = headers.get("id");
@@ -679,30 +782,82 @@ function responseId(headers, body) {
679
782
  return void 0;
680
783
  }
681
784
  }
785
+ function cleanAutoFundErrorMessage(message) {
786
+ const jsonStart = message.indexOf("{");
787
+ if (jsonStart >= 0) {
788
+ try {
789
+ const parsed = JSON.parse(message.slice(jsonStart));
790
+ if (parsed.error) {
791
+ return parsed.error.replace(/^Error:\s*/, "");
792
+ }
793
+ } catch {
794
+ }
795
+ }
796
+ return message;
797
+ }
798
+ function autoFundFailureNote(message) {
799
+ if (/rate limit exceeded/i.test(message)) {
800
+ return "AO token transfer was rate limited. Check that the wallet has enough spendable AO before retrying auto-fund.";
801
+ }
802
+ return "Check the wallet or node ledger before retrying auto-fund; the AO transfer may already have been submitted.";
803
+ }
682
804
  class HyperbeamBundlerClient {
683
805
  autoFund;
806
+ quote;
807
+ seedPreflight;
684
808
  signer;
685
809
  uploader;
686
810
  uploadUrl;
687
- constructor({ autoFund, deployKey, uploadPath, uploader }) {
811
+ constructor({ autoFund, deployKey, quote, uploadPath, uploader }) {
688
812
  const jwk = JSON.parse(Buffer.from(deployKey, "base64").toString("utf8"));
689
813
  this.autoFund = autoFund;
814
+ this.quote = quote ?? { uploader };
690
815
  this.signer = new ArweaveSigner(jwk);
691
816
  this.uploader = uploader;
692
817
  this.uploadUrl = normalizeUploadUrl(uploader, uploadPath);
693
818
  }
694
819
  async uploadFile(args) {
820
+ this.seedPreflight ??= preflightHyperbeamBundlerArBalance(this.uploader);
821
+ await this.seedPreflight;
695
822
  const data = args.file ? typeof args.file === "string" ? fs.readFileSync(args.file) : args.file : await streamToBuffer(args.fileStreamFactory?.() ?? Readable.from([]));
696
823
  const tags = args.dataItemOpts?.tags ?? [];
697
824
  const item = createData(data, this.signer, { tags });
698
825
  await item.sign(this.signer);
699
826
  const raw = Buffer.from(item.getRaw());
700
827
  const localId = item.id || toBase64Url(new DataItem(raw).id);
828
+ const size = { payloadBytes: data.length, signedBytes: raw.length };
829
+ let cost;
701
830
  if (this.autoFund) {
702
- await autoFundQuotedHyperbeamLedger({
703
- ...this.autoFund,
704
- signedBytes: raw.length
705
- });
831
+ const quote = await quoteHyperbeamUpload({ ...this.quote, signedBytes: raw.length });
832
+ cost = { amount: quote.amount, token: "AO" };
833
+ try {
834
+ await autoFundQuotedHyperbeamLedger({
835
+ ...this.autoFund,
836
+ ledgerId: this.autoFund.ledgerId ?? quote.ledgerId,
837
+ minimumBalance: this.autoFund.minimumBalance ?? quote.amount,
838
+ signedBytes: raw.length,
839
+ tokenId: this.autoFund.tokenId ?? quote.tokenId
840
+ });
841
+ } catch (error) {
842
+ const message = cleanAutoFundErrorMessage(
843
+ error instanceof Error ? error.message : String(error)
844
+ );
845
+ throw new Error(
846
+ [
847
+ `HyperBEAM auto-fund failed: ${message}`,
848
+ `Required upload credit: ${formatAoAmount(cost.amount)}`,
849
+ autoFundFailureNote(message),
850
+ await this.paymentHint(false)
851
+ ].filter(Boolean).join("\n\n")
852
+ );
853
+ }
854
+ } else {
855
+ try {
856
+ const quote = await quoteHyperbeamUpload({ ...this.quote, signedBytes: raw.length });
857
+ cost = { amount: quote.amount, token: "AO" };
858
+ } catch {
859
+ cost = void 0;
860
+ }
706
861
  }
707
862
  const res = await fetch(this.uploadUrl, {
708
863
  body: raw,
@@ -714,7 +869,7 @@ class HyperbeamBundlerClient {
714
869
  });
715
870
  const body = await res.text();
716
871
  if (!res.ok) {
717
- const preview = body.replaceAll(/\s+/g, " ").trim().slice(0, 300);
872
+ const preview = responsePreview(body);
718
873
  const paymentHint = res.status === 402 ? await this.paymentHint() : void 0;
719
874
  throw new Error(
720
875
  [
@@ -723,23 +878,36 @@ class HyperbeamBundlerClient {
723
878
  ].filter(Boolean).join("\n\n")
724
879
  );
725
880
  }
726
- return { id: responseId(res.headers, body) || localId };
881
+ return { cost, id: responseId(res.headers, body) || localId, size };
727
882
  }
728
- async paymentHint() {
883
+ async paymentHint(includeAutoFundInstruction = true) {
729
884
  try {
730
885
  return hyperbeamAoFundingHint(
731
- await discoverHyperbeamAoBundlerProfile({ nodeUrl: this.uploader })
886
+ await discoverHyperbeamAoBundlerProfile({ nodeUrl: this.uploader }),
887
+ { includeAutoFundInstruction }
732
888
  );
733
889
  } catch {
734
- return void 0;
890
+ try {
891
+ const operator = await fetch(
892
+ `${this.uploader.replace(/\/+$/, "")}/~meta@1.0/info/address`
893
+ ).then((res) => res.ok ? res.text() : void 0);
894
+ if (!operator?.trim()) return void 0;
895
+ return [
896
+ "The HyperBEAM node requires AO in its local ledger:",
897
+ `- AO: send funds to ${operator.trim()}. Local ledger: ${HYPERBEAM_DEFAULT_LEDGER_ID} at ${HYPERBEAM_DEFAULT_LEDGER_ROUTE}.`,
898
+ includeAutoFundInstruction ? "Use --hyperbeam-auto-fund to transfer AO and import the credit automatically before upload." : void 0
899
+ ].filter(Boolean).join("\n");
900
+ } catch {
901
+ return void 0;
902
+ }
735
903
  }
736
904
  }
737
905
  }
738
- function hyperbeamAoFundingHint(profile) {
906
+ function hyperbeamAoFundingHint(profile, options = {}) {
739
907
  const lines = profile.tokens.map((token) => {
740
908
  const depositAddress = token.depositAddress ?? profile.node?.operator;
741
909
  if (!depositAddress) return;
742
- const label = token.ticker ? `${token.ticker} (${token.id})` : token.id;
910
+ const label = token.ticker === "AO" ? "AO" : token.ticker ? `${token.ticker} (${token.id})` : token.id;
743
911
  const ledger = token.ledgerId ? profile.ledgers.find((candidate) => candidate.id === token.ledgerId) : void 0;
744
912
  const ledgerInfo = ledger ? ` Local ledger: ${ledger.id}${ledger.route ? ` at ${ledger.route}` : ""}.` : "";
745
913
  return `- ${label}: send funds to ${depositAddress}.${ledgerInfo}`;
@@ -748,8 +916,8 @@ function hyperbeamAoFundingHint(profile) {
748
916
  return [
749
917
  "The HyperBEAM node requires AO in its local ledger:",
750
918
  ...lines,
751
- "Use --hyperbeam-auto-fund to transfer AO and import the credit automatically before upload."
752
- ].join("\n");
919
+ options.includeAutoFundInstruction === false ? void 0 : "Use --hyperbeam-auto-fund to transfer AO and import the credit automatically before upload."
920
+ ].filter(Boolean).join("\n");
753
921
  }
754
922
 
755
923
  function getFolderSize(folderPath) {
@@ -793,6 +961,11 @@ async function runUploadWorkflow(deployKey, config, io) {
793
961
  uploadClient = new HyperbeamBundlerClient({
794
962
  autoFund,
795
963
  deployKey,
964
+ quote: {
965
+ ledgerId: config["hyperbeam-ledger-id"],
966
+ tokenId: config["hyperbeam-token-id"],
967
+ uploader: config.uploader
968
+ },
796
969
  uploadPath: config["hyperbeam-upload-path"] ?? "/~bundler@1.0/item?codec-device=ans104@1.0",
797
970
  uploader: config.uploader
798
971
  });
@@ -869,6 +1042,8 @@ async function runUploadWorkflow(deployKey, config, io) {
869
1042
  }
870
1043
  }
871
1044
  let txOrManifestId;
1045
+ let cost;
1046
+ let size;
872
1047
  try {
873
1048
  if (config["deploy-file"]) {
874
1049
  const filePath = expandPath(config["deploy-file"]);
@@ -880,6 +1055,8 @@ async function runUploadWorkflow(deployKey, config, io) {
880
1055
  io.error("File upload failed: no transaction ID returned");
881
1056
  }
882
1057
  txOrManifestId = uploadResult.transactionId;
1058
+ cost = uploadResult.cost;
1059
+ size = uploadResult.size;
883
1060
  if (uploadResult.updatedCache && config["dedupe-cache-max-entries"] > 0) {
884
1061
  cache = cleanupCache(uploadResult.updatedCache, config["dedupe-cache-max-entries"]);
885
1062
  saveCache(cache);
@@ -905,6 +1082,8 @@ async function runUploadWorkflow(deployKey, config, io) {
905
1082
  io.error("Folder upload failed: no transaction ID returned");
906
1083
  }
907
1084
  txOrManifestId = uploadResult.transactionId;
1085
+ cost = uploadResult.cost;
1086
+ size = uploadResult.size;
908
1087
  if (uploadResult.updatedCache && config["dedupe-cache-max-entries"] > 0) {
909
1088
  cache = cleanupCache(uploadResult.updatedCache, config["dedupe-cache-max-entries"]);
910
1089
  saveCache(cache);
@@ -923,8 +1102,12 @@ async function runUploadWorkflow(deployKey, config, io) {
923
1102
  const errorMessage = uploadError instanceof Error ? uploadError.message : String(uploadError);
924
1103
  io.error(`Upload failed: ${errorMessage}`);
925
1104
  }
926
- return txOrManifestId;
1105
+ return {
1106
+ cost,
1107
+ size,
1108
+ transactionId: txOrManifestId
1109
+ };
927
1110
  }
928
1111
 
929
- export { hyperbeamBundlerLink as h, runUploadWorkflow as r };
930
- //# sourceMappingURL=upload-workflow-zlELdPNp.js.map
1112
+ export { chalk as c, hyperbeamBundlerLink as h, runUploadWorkflow as r };
1113
+ //# sourceMappingURL=upload-workflow-CcYev67_.js.map