permaweb-deploy 3.4.3 → 3.4.4

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 (37) hide show
  1. package/README.md +18 -14
  2. package/bin/run.js +0 -0
  3. package/dist/chunks/display-BgIiyBIu.js +60 -0
  4. package/dist/chunks/display-BgIiyBIu.js.map +1 -0
  5. package/dist/chunks/{upload-workflow-zlELdPNp.js → upload-workflow-DMKlwZve.js} +156 -19
  6. package/dist/chunks/upload-workflow-DMKlwZve.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 +99 -30
  10. package/dist/commands/deploy.js.map +1 -1
  11. package/dist/commands/upload.js +28 -5
  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/display.d.ts +5 -0
  24. package/dist/src/utils/display.d.ts.map +1 -0
  25. package/dist/src/utils/hyperbeam-uploader.d.ts +36 -7
  26. package/dist/src/utils/hyperbeam-uploader.d.ts.map +1 -1
  27. package/dist/src/utils/uploader.d.ts +3 -1
  28. package/dist/src/utils/uploader.d.ts.map +1 -1
  29. package/dist/src/workflows/upload-workflow.d.ts +7 -1
  30. package/dist/src/workflows/upload-workflow.d.ts.map +1 -1
  31. package/dist/tests/setup.d.ts +1 -1
  32. package/dist/tests/setup.d.ts.map +1 -1
  33. package/dist/utils/uploader.js +1 -1
  34. package/dist/workflows/upload-workflow.js +2 -2
  35. package/package.json +25 -24
  36. package/dist/chunks/upload-workflow-zlELdPNp.js.map +0 -1
  37. 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,60 @@
1
+ import boxen from 'boxen';
2
+ import chalk from 'chalk';
3
+ import Table from 'cli-table3';
4
+
5
+ const AO_BASE_UNITS = 1000000000000n;
6
+ function formatUploadSize(size) {
7
+ return `${(size.signedBytes ?? size.payloadBytes).toLocaleString()} bytes`;
8
+ }
9
+ function formatUploadCost(cost) {
10
+ if (cost.token !== "AO") {
11
+ return `${cost.amount.toString()}`;
12
+ }
13
+ const whole = cost.amount / AO_BASE_UNITS;
14
+ const fraction = cost.amount % AO_BASE_UNITS;
15
+ const decimal = fraction === 0n ? whole.toString() : `${whole.toString()}.${fraction.toString().padStart(12, "0").replaceAll(/0+$/g, "")}`;
16
+ return `${decimal} AO`;
17
+ }
18
+ function fundingDisplay(section) {
19
+ const fundingLine = section.split("\n").map((line) => line.trim()).find((line) => line.startsWith("- "))?.replace(/^- /, "");
20
+ if (!fundingLine) {
21
+ return section;
22
+ }
23
+ return fundingLine.replace(/^AO: send funds to /, "Sending AO to ").replace(/\. Local ledger:.*$/, "");
24
+ }
25
+ function uploadErrorTable(message, title = "Upload failed") {
26
+ const table = new Table({
27
+ style: { head: [] }
28
+ });
29
+ const sections = message.split(/\n{2,}/).map((section) => section.trim()).filter(Boolean);
30
+ for (const [index, section] of sections.entries()) {
31
+ if (index === 0) {
32
+ table.push(["Error", chalk.red(section)]);
33
+ continue;
34
+ }
35
+ if (section.startsWith("Required upload credit:")) {
36
+ table.push([
37
+ "Required upload credit",
38
+ chalk.blue(section.replace(/^Required upload credit:\s*/, ""))
39
+ ]);
40
+ continue;
41
+ }
42
+ if (section.startsWith("The HyperBEAM node requires AO")) {
43
+ table.push(["Funding", fundingDisplay(section)]);
44
+ continue;
45
+ }
46
+ table.push(["Note", section]);
47
+ }
48
+ return boxen(`${chalk.red.bold(title)}
49
+
50
+ ${table.toString()}`, {
51
+ borderColor: "red",
52
+ borderStyle: "round",
53
+ padding: 1,
54
+ title: chalk.bold("Permaweb Deploy"),
55
+ titleAlignment: "center"
56
+ });
57
+ }
58
+
59
+ export { formatUploadCost as a, formatUploadSize as f, uploadErrorTable as u };
60
+ //# sourceMappingURL=display-BgIiyBIu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display-BgIiyBIu.js","sources":["../../src/utils/display.ts"],"sourcesContent":["import boxen from 'boxen'\nimport chalk from 'chalk'\n// eslint-disable-next-line import/no-named-as-default\nimport Table from 'cli-table3'\n\nimport type { UploadCost, UploadSize } from './hyperbeam-uploader.js'\n\nconst AO_BASE_UNITS = 1_000_000_000_000n\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\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 uploadErrorTable(message: string, title = 'Upload failed'): string {\n const table = new Table({\n style: { head: [] },\n })\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 table.push(['Error', chalk.red(section)])\n continue\n }\n\n if (section.startsWith('Required upload credit:')) {\n table.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 table.push(['Funding', fundingDisplay(section)])\n continue\n }\n\n table.push(['Note', section])\n }\n\n return boxen(`${chalk.red.bold(title)}\\n\\n${table.toString()}`, {\n borderColor: 'red',\n borderStyle: 'round',\n padding: 1,\n title: chalk.bold('Permaweb Deploy'),\n titleAlignment: 'center',\n })\n}\n"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { TurboFactory, ETHToTokenAmount, ARIOToTokenAmount, OnDemandFunding } from '@ardrive/turbo-sdk';
4
4
  import chalk from 'chalk';
5
5
  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';
6
+ import { l as loadCache, u as uploadFile, c as cleanupCache, s as saveCache, a as uploadFolder } from './uploader-CIHu22Fw.js';
7
7
  import { createHash } from 'node:crypto';
8
8
  import { createRequire } from 'node:module';
9
9
  import { Readable } from 'node:stream';
@@ -548,6 +548,8 @@ async function fetchHyperbeamOperatorAddress(fetcher, nodeUrl) {
548
548
 
549
549
  const require$1 = createRequire(import.meta.url);
550
550
  const { ArweaveSigner, DataItem, createData } = require$1("@dha-team/arbundles");
551
+ const AO_BASE_UNITS = 1000000000000n;
552
+ const ARWEAVE_GATEWAY = "https://arweave.net";
551
553
  async function readableToBuffer(stream) {
552
554
  const chunks = [];
553
555
  for await (const chunk of stream) {
@@ -593,6 +595,24 @@ function parseHyperbeamFundAmount(value) {
593
595
  }
594
596
  return BigInt(value);
595
597
  }
598
+ function formatAoAmount(amount) {
599
+ const whole = amount / AO_BASE_UNITS;
600
+ const fraction = amount % AO_BASE_UNITS;
601
+ if (fraction === 0n) {
602
+ return `${whole.toString()} AO`;
603
+ }
604
+ return `${whole.toString()}.${fraction.toString().padStart(12, "0").replaceAll(/0+$/g, "")} AO`;
605
+ }
606
+ function responsePreview(body) {
607
+ const preview = body.replaceAll(/\s+/g, " ").trim();
608
+ if (!preview) {
609
+ return void 0;
610
+ }
611
+ if (/^(<!doctype html\b|<html\b)/i.test(preview)) {
612
+ return "HTML error response";
613
+ }
614
+ return preview.slice(0, 300);
615
+ }
596
616
  async function ensureHyperbeamCredit(options, profile) {
597
617
  const jwk = JSON.parse(Buffer.from(options.deployKey, "base64").toString("utf8"));
598
618
  const recipient = arweaveAddressFromJwk(jwk);
@@ -663,9 +683,46 @@ async function autoFundQuotedHyperbeamLedger(options) {
663
683
  profile
664
684
  );
665
685
  }
666
- function hyperbeamBundlerLink(uploader, id) {
686
+ async function quoteHyperbeamUpload(options) {
687
+ const profile = await discoverHyperbeamAoBundlerProfile({
688
+ ledgerId: options.ledgerId,
689
+ nodeUrl: options.uploader,
690
+ tokenId: options.tokenId
691
+ });
692
+ const quote = await new HyperbalanceClient({ nodeUrl: options.uploader }).quoteAuto({
693
+ action: options.quoteAction ?? "hyperbeam-upload",
694
+ params: { bytes: options.signedBytes },
695
+ profile
696
+ });
697
+ return { amount: quote.amount, ledgerId: quote.ledgerId, tokenId: quote.tokenId };
698
+ }
699
+ function hyperbeamBundlerLink(uploader, id, isManifest = false) {
667
700
  const normalizedBase = uploader.endsWith("/") ? uploader : `${uploader}/`;
668
- return new URL(`~arweave@2.9/raw=${encodeURIComponent(id)}`, normalizedBase).toString();
701
+ return new URL(`${encodeURIComponent(id)}${isManifest ? "/" : ""}`, normalizedBase).toString();
702
+ }
703
+ async function preflightHyperbeamBundlerArBalance(uploader) {
704
+ const nodeUrl = uploader.replace(/\/+$/, "");
705
+ const addressRes = await fetch(`${nodeUrl}/~meta@1.0/info/address`);
706
+ if (!addressRes.ok) {
707
+ throw new Error(`HyperBEAM bundler address check failed with HTTP ${addressRes.status}`);
708
+ }
709
+ const address = (await addressRes.text()).trim();
710
+ if (!address) {
711
+ throw new Error("HyperBEAM bundler address check returned an empty address");
712
+ }
713
+ const balanceRes = await fetch(`${ARWEAVE_GATEWAY}/wallet/${encodeURIComponent(address)}/balance`);
714
+ if (!balanceRes.ok) {
715
+ throw new Error(`HyperBEAM bundler AR balance check failed with HTTP ${balanceRes.status}`);
716
+ }
717
+ const balance = (await balanceRes.text()).trim();
718
+ if (!/^\d+$/.test(balance)) {
719
+ throw new Error("HyperBEAM bundler AR balance check returned an invalid balance");
720
+ }
721
+ if (BigInt(balance) === 0n) {
722
+ throw new Error(
723
+ `HyperBEAM bundler wallet ${address} has 0 AR; upload aborted because the node cannot seed data to Arweave.`
724
+ );
725
+ }
669
726
  }
670
727
  function responseId(headers, body) {
671
728
  const headerId = headers.get("id");
@@ -679,30 +736,82 @@ function responseId(headers, body) {
679
736
  return void 0;
680
737
  }
681
738
  }
739
+ function cleanAutoFundErrorMessage(message) {
740
+ const jsonStart = message.indexOf("{");
741
+ if (jsonStart >= 0) {
742
+ try {
743
+ const parsed = JSON.parse(message.slice(jsonStart));
744
+ if (parsed.error) {
745
+ return parsed.error.replace(/^Error:\s*/, "");
746
+ }
747
+ } catch {
748
+ }
749
+ }
750
+ return message;
751
+ }
752
+ function autoFundFailureNote(message) {
753
+ if (/rate limit exceeded/i.test(message)) {
754
+ return "AO token transfer was rate limited. Check that the wallet has enough spendable AO before retrying auto-fund.";
755
+ }
756
+ return "Check the wallet or node ledger before retrying auto-fund; the AO transfer may already have been submitted.";
757
+ }
682
758
  class HyperbeamBundlerClient {
683
759
  autoFund;
760
+ quote;
761
+ seedPreflight;
684
762
  signer;
685
763
  uploader;
686
764
  uploadUrl;
687
- constructor({ autoFund, deployKey, uploadPath, uploader }) {
765
+ constructor({ autoFund, deployKey, quote, uploadPath, uploader }) {
688
766
  const jwk = JSON.parse(Buffer.from(deployKey, "base64").toString("utf8"));
689
767
  this.autoFund = autoFund;
768
+ this.quote = quote ?? { uploader };
690
769
  this.signer = new ArweaveSigner(jwk);
691
770
  this.uploader = uploader;
692
771
  this.uploadUrl = normalizeUploadUrl(uploader, uploadPath);
693
772
  }
694
773
  async uploadFile(args) {
774
+ this.seedPreflight ??= preflightHyperbeamBundlerArBalance(this.uploader);
775
+ await this.seedPreflight;
695
776
  const data = args.file ? typeof args.file === "string" ? fs.readFileSync(args.file) : args.file : await streamToBuffer(args.fileStreamFactory?.() ?? Readable.from([]));
696
777
  const tags = args.dataItemOpts?.tags ?? [];
697
778
  const item = createData(data, this.signer, { tags });
698
779
  await item.sign(this.signer);
699
780
  const raw = Buffer.from(item.getRaw());
700
781
  const localId = item.id || toBase64Url(new DataItem(raw).id);
782
+ const size = { payloadBytes: data.length, signedBytes: raw.length };
783
+ let cost;
701
784
  if (this.autoFund) {
702
- await autoFundQuotedHyperbeamLedger({
703
- ...this.autoFund,
704
- signedBytes: raw.length
705
- });
785
+ const quote = await quoteHyperbeamUpload({ ...this.quote, signedBytes: raw.length });
786
+ cost = { amount: quote.amount, token: "AO" };
787
+ try {
788
+ await autoFundQuotedHyperbeamLedger({
789
+ ...this.autoFund,
790
+ ledgerId: this.autoFund.ledgerId ?? quote.ledgerId,
791
+ minimumBalance: this.autoFund.minimumBalance ?? quote.amount,
792
+ signedBytes: raw.length,
793
+ tokenId: this.autoFund.tokenId ?? quote.tokenId
794
+ });
795
+ } catch (error) {
796
+ const message = cleanAutoFundErrorMessage(
797
+ error instanceof Error ? error.message : String(error)
798
+ );
799
+ throw new Error(
800
+ [
801
+ `HyperBEAM auto-fund failed: ${message}`,
802
+ `Required upload credit: ${formatAoAmount(cost.amount)}`,
803
+ autoFundFailureNote(message),
804
+ await this.paymentHint(false)
805
+ ].filter(Boolean).join("\n\n")
806
+ );
807
+ }
808
+ } else {
809
+ try {
810
+ const quote = await quoteHyperbeamUpload({ ...this.quote, signedBytes: raw.length });
811
+ cost = { amount: quote.amount, token: "AO" };
812
+ } catch {
813
+ cost = void 0;
814
+ }
706
815
  }
707
816
  const res = await fetch(this.uploadUrl, {
708
817
  body: raw,
@@ -714,7 +823,7 @@ class HyperbeamBundlerClient {
714
823
  });
715
824
  const body = await res.text();
716
825
  if (!res.ok) {
717
- const preview = body.replaceAll(/\s+/g, " ").trim().slice(0, 300);
826
+ const preview = responsePreview(body);
718
827
  const paymentHint = res.status === 402 ? await this.paymentHint() : void 0;
719
828
  throw new Error(
720
829
  [
@@ -723,23 +832,36 @@ class HyperbeamBundlerClient {
723
832
  ].filter(Boolean).join("\n\n")
724
833
  );
725
834
  }
726
- return { id: responseId(res.headers, body) || localId };
835
+ return { cost, id: responseId(res.headers, body) || localId, size };
727
836
  }
728
- async paymentHint() {
837
+ async paymentHint(includeAutoFundInstruction = true) {
729
838
  try {
730
839
  return hyperbeamAoFundingHint(
731
- await discoverHyperbeamAoBundlerProfile({ nodeUrl: this.uploader })
840
+ await discoverHyperbeamAoBundlerProfile({ nodeUrl: this.uploader }),
841
+ { includeAutoFundInstruction }
732
842
  );
733
843
  } catch {
734
- return void 0;
844
+ try {
845
+ const operator = await fetch(
846
+ `${this.uploader.replace(/\/+$/, "")}/~meta@1.0/info/address`
847
+ ).then((res) => res.ok ? res.text() : void 0);
848
+ if (!operator?.trim()) return void 0;
849
+ return [
850
+ "The HyperBEAM node requires AO in its local ledger:",
851
+ `- AO: send funds to ${operator.trim()}. Local ledger: ${HYPERBEAM_DEFAULT_LEDGER_ID} at ${HYPERBEAM_DEFAULT_LEDGER_ROUTE}.`,
852
+ includeAutoFundInstruction ? "Use --hyperbeam-auto-fund to transfer AO and import the credit automatically before upload." : void 0
853
+ ].filter(Boolean).join("\n");
854
+ } catch {
855
+ return void 0;
856
+ }
735
857
  }
736
858
  }
737
859
  }
738
- function hyperbeamAoFundingHint(profile) {
860
+ function hyperbeamAoFundingHint(profile, options = {}) {
739
861
  const lines = profile.tokens.map((token) => {
740
862
  const depositAddress = token.depositAddress ?? profile.node?.operator;
741
863
  if (!depositAddress) return;
742
- const label = token.ticker ? `${token.ticker} (${token.id})` : token.id;
864
+ const label = token.ticker === "AO" ? "AO" : token.ticker ? `${token.ticker} (${token.id})` : token.id;
743
865
  const ledger = token.ledgerId ? profile.ledgers.find((candidate) => candidate.id === token.ledgerId) : void 0;
744
866
  const ledgerInfo = ledger ? ` Local ledger: ${ledger.id}${ledger.route ? ` at ${ledger.route}` : ""}.` : "";
745
867
  return `- ${label}: send funds to ${depositAddress}.${ledgerInfo}`;
@@ -748,8 +870,8 @@ function hyperbeamAoFundingHint(profile) {
748
870
  return [
749
871
  "The HyperBEAM node requires AO in its local ledger:",
750
872
  ...lines,
751
- "Use --hyperbeam-auto-fund to transfer AO and import the credit automatically before upload."
752
- ].join("\n");
873
+ options.includeAutoFundInstruction === false ? void 0 : "Use --hyperbeam-auto-fund to transfer AO and import the credit automatically before upload."
874
+ ].filter(Boolean).join("\n");
753
875
  }
754
876
 
755
877
  function getFolderSize(folderPath) {
@@ -793,6 +915,11 @@ async function runUploadWorkflow(deployKey, config, io) {
793
915
  uploadClient = new HyperbeamBundlerClient({
794
916
  autoFund,
795
917
  deployKey,
918
+ quote: {
919
+ ledgerId: config["hyperbeam-ledger-id"],
920
+ tokenId: config["hyperbeam-token-id"],
921
+ uploader: config.uploader
922
+ },
796
923
  uploadPath: config["hyperbeam-upload-path"] ?? "/~bundler@1.0/item?codec-device=ans104@1.0",
797
924
  uploader: config.uploader
798
925
  });
@@ -869,6 +996,8 @@ async function runUploadWorkflow(deployKey, config, io) {
869
996
  }
870
997
  }
871
998
  let txOrManifestId;
999
+ let cost;
1000
+ let size;
872
1001
  try {
873
1002
  if (config["deploy-file"]) {
874
1003
  const filePath = expandPath(config["deploy-file"]);
@@ -880,6 +1009,8 @@ async function runUploadWorkflow(deployKey, config, io) {
880
1009
  io.error("File upload failed: no transaction ID returned");
881
1010
  }
882
1011
  txOrManifestId = uploadResult.transactionId;
1012
+ cost = uploadResult.cost;
1013
+ size = uploadResult.size;
883
1014
  if (uploadResult.updatedCache && config["dedupe-cache-max-entries"] > 0) {
884
1015
  cache = cleanupCache(uploadResult.updatedCache, config["dedupe-cache-max-entries"]);
885
1016
  saveCache(cache);
@@ -905,6 +1036,8 @@ async function runUploadWorkflow(deployKey, config, io) {
905
1036
  io.error("Folder upload failed: no transaction ID returned");
906
1037
  }
907
1038
  txOrManifestId = uploadResult.transactionId;
1039
+ cost = uploadResult.cost;
1040
+ size = uploadResult.size;
908
1041
  if (uploadResult.updatedCache && config["dedupe-cache-max-entries"] > 0) {
909
1042
  cache = cleanupCache(uploadResult.updatedCache, config["dedupe-cache-max-entries"]);
910
1043
  saveCache(cache);
@@ -923,8 +1056,12 @@ async function runUploadWorkflow(deployKey, config, io) {
923
1056
  const errorMessage = uploadError instanceof Error ? uploadError.message : String(uploadError);
924
1057
  io.error(`Upload failed: ${errorMessage}`);
925
1058
  }
926
- return txOrManifestId;
1059
+ return {
1060
+ cost,
1061
+ size,
1062
+ transactionId: txOrManifestId
1063
+ };
927
1064
  }
928
1065
 
929
1066
  export { hyperbeamBundlerLink as h, runUploadWorkflow as r };
930
- //# sourceMappingURL=upload-workflow-zlELdPNp.js.map
1067
+ //# sourceMappingURL=upload-workflow-DMKlwZve.js.map