permaweb-deploy 4.0.0 → 5.0.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/README.md +174 -511
- package/dist/chunks/chalk-BP_cCc9v.js +49 -0
- package/dist/chunks/chalk-BP_cCc9v.js.map +1 -0
- package/dist/chunks/{display-WYdGiMxZ.js → display-CZV6KUcG.js} +2 -2
- package/dist/chunks/{display-WYdGiMxZ.js.map → display-CZV6KUcG.js.map} +1 -1
- package/dist/chunks/upload-workflow-f3hExxtV.js +620 -0
- package/dist/chunks/upload-workflow-f3hExxtV.js.map +1 -0
- package/dist/chunks/{uploader-CC-qBdlV.js → uploader-Bz9jraY2.js} +15 -17
- package/dist/chunks/uploader-Bz9jraY2.js.map +1 -0
- package/dist/commands/deploy.js +72 -98
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/hyperbeam-uploaders.js +48 -0
- package/dist/commands/hyperbeam-uploaders.js.map +1 -0
- package/dist/commands/upload.js +11 -12
- package/dist/commands/upload.js.map +1 -1
- package/dist/constants/flags.js +66 -97
- package/dist/constants/flags.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/prompts/names.js +13 -0
- package/dist/prompts/names.js.map +1 -0
- package/dist/src/commands/deploy.d.ts +1 -1
- package/dist/src/commands/deploy.d.ts.map +1 -1
- package/dist/src/commands/hyperbeam-uploaders.d.ts +14 -0
- package/dist/src/commands/hyperbeam-uploaders.d.ts.map +1 -0
- package/dist/src/commands/upload.d.ts +1 -1
- package/dist/src/commands/upload.d.ts.map +1 -1
- package/dist/src/constants/flags.d.ts +271 -100
- package/dist/src/constants/flags.d.ts.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/prompts/names.d.ts +2 -0
- package/dist/src/prompts/names.d.ts.map +1 -0
- package/dist/src/types/index.d.ts +0 -29
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/__tests__/hyperbeam-uploaders-cli.test.d.ts +2 -0
- package/dist/src/utils/__tests__/hyperbeam-uploaders-cli.test.d.ts.map +1 -0
- package/dist/src/utils/__tests__/names.test.d.ts +2 -0
- package/dist/src/utils/__tests__/names.test.d.ts.map +1 -0
- package/dist/src/utils/__tests__/permawebos-bundlers.test.d.ts +2 -0
- package/dist/src/utils/__tests__/permawebos-bundlers.test.d.ts.map +1 -0
- package/dist/src/utils/config-resolver.d.ts +29 -11
- package/dist/src/utils/config-resolver.d.ts.map +1 -1
- package/dist/src/utils/constants.d.ts +0 -2
- package/dist/src/utils/constants.d.ts.map +1 -1
- package/dist/src/utils/hyperbeam-uploader.d.ts +7 -0
- package/dist/src/utils/hyperbeam-uploader.d.ts.map +1 -1
- package/dist/src/utils/hyperbeam-uploaders-cli.d.ts +7 -0
- package/dist/src/utils/hyperbeam-uploaders-cli.d.ts.map +1 -0
- package/dist/src/utils/legacy-bundler-uploader.d.ts +29 -0
- package/dist/src/utils/legacy-bundler-uploader.d.ts.map +1 -0
- package/dist/src/utils/names.d.ts +46 -0
- package/dist/src/utils/names.d.ts.map +1 -0
- package/dist/src/utils/permawebos-bundlers.d.ts +28 -0
- package/dist/src/utils/permawebos-bundlers.d.ts.map +1 -0
- package/dist/src/utils/signer.d.ts +4 -6
- package/dist/src/utils/signer.d.ts.map +1 -1
- package/dist/src/utils/uploader.d.ts +5 -7
- package/dist/src/utils/uploader.d.ts.map +1 -1
- package/dist/src/utils/validators.d.ts +13 -20
- package/dist/src/utils/validators.d.ts.map +1 -1
- package/dist/src/workflows/upload-workflow.d.ts +3 -4
- package/dist/src/workflows/upload-workflow.d.ts.map +1 -1
- package/dist/tests/mocks/{turbo-handlers.d.ts → legacy-handlers.d.ts} +12 -27
- package/dist/tests/mocks/legacy-handlers.d.ts.map +1 -0
- package/dist/tests/setup.d.ts +2 -2
- package/dist/tests/setup.d.ts.map +1 -1
- package/dist/tests/types/payment-service.d.ts +2 -2
- package/dist/tests/types/payment-service.d.ts.map +1 -1
- package/dist/tests/types/upload-service.d.ts +1 -1
- package/dist/tests/types/upload-service.d.ts.map +1 -1
- package/dist/utils/config-resolver.js +4 -1
- package/dist/utils/config-resolver.js.map +1 -1
- package/dist/utils/constants.js +1 -3
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/hyperbeam-uploaders-cli.js +32 -0
- package/dist/utils/hyperbeam-uploaders-cli.js.map +1 -0
- package/dist/utils/names.js +157 -0
- package/dist/utils/names.js.map +1 -0
- package/dist/utils/permawebos-bundlers.js +120 -0
- package/dist/utils/permawebos-bundlers.js.map +1 -0
- package/dist/utils/signer.js +3 -2
- package/dist/utils/signer.js.map +1 -1
- package/dist/utils/uploader.js +1 -1
- package/dist/utils/validators.js +3 -39
- package/dist/utils/validators.js.map +1 -1
- package/dist/workflows/upload-workflow.js +5 -6
- package/dist/workflows/upload-workflow.js.map +1 -1
- package/package.json +5 -5
- package/dist/chunks/upload-workflow-BnN8mhkm.js +0 -1195
- package/dist/chunks/upload-workflow-BnN8mhkm.js.map +0 -1
- package/dist/chunks/uploader-CC-qBdlV.js.map +0 -1
- package/dist/prompts/arns.js +0 -89
- package/dist/prompts/arns.js.map +0 -1
- package/dist/src/prompts/arns.d.ts +0 -13
- package/dist/src/prompts/arns.d.ts.map +0 -1
- package/dist/tests/mocks/turbo-handlers.d.ts.map +0 -1
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { createHash, randomInt } from 'node:crypto';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { l as loadCache, u as uploadFile, c as cleanupCache, s as saveCache, a as uploadFolder } from './uploader-Bz9jraY2.js';
|
|
4
|
+
import { c as chalk } from './chalk-BP_cCc9v.js';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { Readable } from 'node:stream';
|
|
8
|
+
import { createDataItemSigner, message } from '@permaweb/aoconnect';
|
|
9
|
+
import { discoverHyperbeamAoBundlerProfile, HYPERBEAM_DEFAULT_LEDGER_ID, HYPERBEAM_DEFAULT_LEDGER_ROUTE, HyperbalanceClient, AoTokenTransferAdapter, waitForAoAssignmentSlot, DEFAULT_AO_TOKEN_ID } from '@permaweb/hyperbalance';
|
|
10
|
+
import { createSigner } from '../utils/signer.js';
|
|
11
|
+
import { expandPath } from '../utils/path.js';
|
|
12
|
+
import { fetchActivePermawebOSBundlers } from '../utils/permawebos-bundlers.js';
|
|
13
|
+
|
|
14
|
+
const require$2 = createRequire(import.meta.url);
|
|
15
|
+
const { ArweaveSigner, DataItem: DataItem$1, createData: createData$1 } = require$2("@dha-team/arbundles");
|
|
16
|
+
const AO_BASE_UNITS = 1000000000000n;
|
|
17
|
+
const ARWEAVE_GATEWAY = "https://arweave.net";
|
|
18
|
+
async function readableToBuffer$1(stream) {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
for await (const chunk of stream) {
|
|
21
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
22
|
+
}
|
|
23
|
+
return Buffer.concat(chunks);
|
|
24
|
+
}
|
|
25
|
+
async function streamToBuffer$1(stream) {
|
|
26
|
+
if (Buffer.isBuffer(stream)) {
|
|
27
|
+
return stream;
|
|
28
|
+
}
|
|
29
|
+
if (stream instanceof Uint8Array) {
|
|
30
|
+
return Buffer.from(stream);
|
|
31
|
+
}
|
|
32
|
+
if (stream instanceof Readable) {
|
|
33
|
+
return readableToBuffer$1(stream);
|
|
34
|
+
}
|
|
35
|
+
if (stream && typeof stream.getReader === "function") {
|
|
36
|
+
return readableToBuffer$1(Readable.fromWeb(stream));
|
|
37
|
+
}
|
|
38
|
+
throw new Error("Unsupported upload stream type");
|
|
39
|
+
}
|
|
40
|
+
function toBase64Url$1(value) {
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
return Buffer.from(value).toString("base64url");
|
|
45
|
+
}
|
|
46
|
+
function normalizeUploadUrl(base, uploadPath) {
|
|
47
|
+
const normalizedBase = base.endsWith("/") ? base : `${base}/`;
|
|
48
|
+
const cleanPath = uploadPath.startsWith("/") ? uploadPath.slice(1) : uploadPath;
|
|
49
|
+
return new URL(cleanPath, normalizedBase).toString();
|
|
50
|
+
}
|
|
51
|
+
function arweaveAddressFromJwk(jwk) {
|
|
52
|
+
if (typeof jwk.n !== "string") {
|
|
53
|
+
throw new TypeError('Arweave JWK is missing modulus field "n"');
|
|
54
|
+
}
|
|
55
|
+
return createHash("sha256").update(Buffer.from(jwk.n, "base64url")).digest("base64url");
|
|
56
|
+
}
|
|
57
|
+
function parseHyperbeamFundAmount(value) {
|
|
58
|
+
if (!/^[1-9]\d*$/.test(value)) {
|
|
59
|
+
throw new Error("--hyperbeam-fund-amount must be a positive integer in token base units");
|
|
60
|
+
}
|
|
61
|
+
return BigInt(value);
|
|
62
|
+
}
|
|
63
|
+
function formatAoAmount(amount) {
|
|
64
|
+
const whole = amount / AO_BASE_UNITS;
|
|
65
|
+
const fraction = amount % AO_BASE_UNITS;
|
|
66
|
+
if (fraction === 0n) {
|
|
67
|
+
return `${whole.toString()} AO`;
|
|
68
|
+
}
|
|
69
|
+
return `${whole.toString()}.${fraction.toString().padStart(12, "0").replaceAll(/0+$/g, "")} AO`;
|
|
70
|
+
}
|
|
71
|
+
function responsePreview$1(body) {
|
|
72
|
+
const preview = body.replaceAll(/\s+/g, " ").trim();
|
|
73
|
+
if (!preview) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
if (/^(<!doctype html\b|<html\b)/i.test(preview)) {
|
|
77
|
+
return "HTML error response";
|
|
78
|
+
}
|
|
79
|
+
return preview.slice(0, 300);
|
|
80
|
+
}
|
|
81
|
+
async function ensureHyperbeamCredit(options, profile) {
|
|
82
|
+
const jwk = JSON.parse(Buffer.from(options.deployKey, "base64").toString("utf8"));
|
|
83
|
+
const recipient = arweaveAddressFromJwk(jwk);
|
|
84
|
+
const signer = createDataItemSigner(jwk);
|
|
85
|
+
const client = new HyperbalanceClient({ nodeUrl: options.uploader });
|
|
86
|
+
const adapter = new AoTokenTransferAdapter({
|
|
87
|
+
async inferSender() {
|
|
88
|
+
return recipient;
|
|
89
|
+
},
|
|
90
|
+
async message(input) {
|
|
91
|
+
return message({
|
|
92
|
+
data: input.data ?? "",
|
|
93
|
+
process: input.process,
|
|
94
|
+
signer,
|
|
95
|
+
tags: input.tags
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
async waitForAssignmentSlot(messageId, context) {
|
|
99
|
+
return waitForAoAssignmentSlot({
|
|
100
|
+
messageId,
|
|
101
|
+
pollMs: options.aoPollMs,
|
|
102
|
+
processId: context.processId,
|
|
103
|
+
stateUrl: options.aoStateUrl,
|
|
104
|
+
timeoutMs: options.aoTimeoutMs
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return client.ensureCreditAuto({
|
|
109
|
+
ledgerId: options.ledgerId,
|
|
110
|
+
minimumBalance: options.minimumBalance,
|
|
111
|
+
profile,
|
|
112
|
+
recipient,
|
|
113
|
+
tokenId: options.tokenId ?? DEFAULT_AO_TOKEN_ID,
|
|
114
|
+
transferAdapter: adapter
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function autoFundQuotedHyperbeamLedger(options) {
|
|
118
|
+
const profile = await discoverHyperbeamAoBundlerProfile({
|
|
119
|
+
ledgerId: options.ledgerId,
|
|
120
|
+
nodeUrl: options.uploader,
|
|
121
|
+
tokenId: options.tokenId
|
|
122
|
+
});
|
|
123
|
+
const client = new HyperbalanceClient({ nodeUrl: options.uploader });
|
|
124
|
+
let { ledgerId } = options;
|
|
125
|
+
let { minimumBalance } = options;
|
|
126
|
+
let { tokenId } = options;
|
|
127
|
+
if (minimumBalance === void 0) {
|
|
128
|
+
const quote = await client.quoteAuto({
|
|
129
|
+
action: options.quoteAction ?? "hyperbeam-upload",
|
|
130
|
+
params: { bytes: options.signedBytes },
|
|
131
|
+
profile
|
|
132
|
+
});
|
|
133
|
+
minimumBalance = quote.amount;
|
|
134
|
+
ledgerId ??= quote.ledgerId;
|
|
135
|
+
tokenId ??= quote.tokenId;
|
|
136
|
+
}
|
|
137
|
+
return ensureHyperbeamCredit(
|
|
138
|
+
{
|
|
139
|
+
aoPollMs: options.aoPollMs,
|
|
140
|
+
aoStateUrl: options.aoStateUrl,
|
|
141
|
+
aoTimeoutMs: options.aoTimeoutMs,
|
|
142
|
+
deployKey: options.deployKey,
|
|
143
|
+
ledgerId,
|
|
144
|
+
minimumBalance,
|
|
145
|
+
tokenId,
|
|
146
|
+
uploader: options.uploader
|
|
147
|
+
},
|
|
148
|
+
profile
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
async function quoteHyperbeamUpload(options) {
|
|
152
|
+
const profile = await discoverHyperbeamAoBundlerProfile({
|
|
153
|
+
ledgerId: options.ledgerId,
|
|
154
|
+
nodeUrl: options.uploader,
|
|
155
|
+
tokenId: options.tokenId
|
|
156
|
+
});
|
|
157
|
+
const quote = await new HyperbalanceClient({ nodeUrl: options.uploader }).quoteAuto({
|
|
158
|
+
action: options.quoteAction ?? "hyperbeam-upload",
|
|
159
|
+
params: { bytes: options.signedBytes },
|
|
160
|
+
profile
|
|
161
|
+
});
|
|
162
|
+
return { amount: quote.amount, ledgerId: quote.ledgerId, tokenId: quote.tokenId };
|
|
163
|
+
}
|
|
164
|
+
function hyperbeamBundlerLink(uploader, id, isManifest = false) {
|
|
165
|
+
const normalizedBase = uploader.endsWith("/") ? uploader : `${uploader}/`;
|
|
166
|
+
return new URL(`${encodeURIComponent(id)}${isManifest ? "/" : ""}`, normalizedBase).toString();
|
|
167
|
+
}
|
|
168
|
+
async function preflightHyperbeamBundlerArBalance(uploader) {
|
|
169
|
+
const nodeUrl = uploader.replace(/\/+$/, "");
|
|
170
|
+
const addressRes = await fetch(`${nodeUrl}/~meta@1.0/info/address`);
|
|
171
|
+
if (!addressRes.ok) {
|
|
172
|
+
throw new Error(`HyperBEAM bundler address check failed with HTTP ${addressRes.status}`);
|
|
173
|
+
}
|
|
174
|
+
const address = (await addressRes.text()).trim();
|
|
175
|
+
if (!address) {
|
|
176
|
+
throw new Error("HyperBEAM bundler address check returned an empty address");
|
|
177
|
+
}
|
|
178
|
+
const balanceRes = await fetch(`${ARWEAVE_GATEWAY}/wallet/${encodeURIComponent(address)}/balance`);
|
|
179
|
+
if (!balanceRes.ok) {
|
|
180
|
+
throw new Error(`HyperBEAM bundler AR balance check failed with HTTP ${balanceRes.status}`);
|
|
181
|
+
}
|
|
182
|
+
const balance = (await balanceRes.text()).trim();
|
|
183
|
+
if (!/^\d+$/.test(balance)) {
|
|
184
|
+
throw new Error("HyperBEAM bundler AR balance check returned an invalid balance");
|
|
185
|
+
}
|
|
186
|
+
if (BigInt(balance) === 0n) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`HyperBEAM bundler wallet ${address} has 0 AR; upload aborted because the node cannot seed data to Arweave.`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function responseId(headers, body) {
|
|
193
|
+
const headerId = headers.get("id");
|
|
194
|
+
if (headerId) {
|
|
195
|
+
return headerId;
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
const parsed = JSON.parse(body);
|
|
199
|
+
return parsed.id || parsed.body?.id;
|
|
200
|
+
} catch {
|
|
201
|
+
return void 0;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function postHyperbeamDataItem(uploadUrl, raw, localId) {
|
|
205
|
+
const res = await fetch(uploadUrl, {
|
|
206
|
+
body: raw,
|
|
207
|
+
headers: {
|
|
208
|
+
accept: "application/json, text/plain, */*",
|
|
209
|
+
"content-type": "application/octet-stream"
|
|
210
|
+
},
|
|
211
|
+
method: "POST"
|
|
212
|
+
});
|
|
213
|
+
const body = await res.text();
|
|
214
|
+
if (!res.ok) {
|
|
215
|
+
const preview = responsePreview$1(body);
|
|
216
|
+
const itemContext = localId ? ` for local data item ${localId}` : "";
|
|
217
|
+
throw new Error(
|
|
218
|
+
`HyperBEAM bundler upload failed${itemContext} with HTTP ${res.status}${preview ? `: ${preview}` : ""}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return { body, id: responseId(res.headers, body) };
|
|
222
|
+
}
|
|
223
|
+
function cleanAutoFundErrorMessage(message) {
|
|
224
|
+
const jsonStart = message.indexOf("{");
|
|
225
|
+
if (jsonStart >= 0) {
|
|
226
|
+
try {
|
|
227
|
+
const parsed = JSON.parse(message.slice(jsonStart));
|
|
228
|
+
if (parsed.error) {
|
|
229
|
+
return parsed.error.replace(/^Error:\s*/, "");
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return message;
|
|
235
|
+
}
|
|
236
|
+
function autoFundFailureNote(message) {
|
|
237
|
+
if (/rate limit exceeded/i.test(message)) {
|
|
238
|
+
return "AO token transfer was rate limited. Check that the wallet has enough spendable AO before retrying auto-fund.";
|
|
239
|
+
}
|
|
240
|
+
return "Check the wallet or node ledger before retrying auto-fund; the AO transfer may already have been submitted.";
|
|
241
|
+
}
|
|
242
|
+
function autoFundUnavailableMessage(message) {
|
|
243
|
+
return [
|
|
244
|
+
"HyperBEAM auto-fund was requested, but this node is not hyperbalance-compatible for this upload.",
|
|
245
|
+
`Auto-fund compatibility check failed: ${message}`,
|
|
246
|
+
"Attempted direct upload instead."
|
|
247
|
+
].join("\n");
|
|
248
|
+
}
|
|
249
|
+
class HyperbeamBundlerClient {
|
|
250
|
+
autoFund;
|
|
251
|
+
quote;
|
|
252
|
+
seedPreflight;
|
|
253
|
+
signer;
|
|
254
|
+
uploader;
|
|
255
|
+
uploadUrl;
|
|
256
|
+
constructor({ autoFund, deployKey, quote, uploadPath, uploader }) {
|
|
257
|
+
const jwk = JSON.parse(Buffer.from(deployKey, "base64").toString("utf8"));
|
|
258
|
+
this.autoFund = autoFund;
|
|
259
|
+
this.quote = quote ?? { uploader };
|
|
260
|
+
this.signer = new ArweaveSigner(jwk);
|
|
261
|
+
this.uploader = uploader;
|
|
262
|
+
this.uploadUrl = normalizeUploadUrl(uploader, uploadPath);
|
|
263
|
+
}
|
|
264
|
+
async uploadFile(args) {
|
|
265
|
+
const data = args.file ? typeof args.file === "string" ? fs.readFileSync(args.file) : args.file : await streamToBuffer$1(args.fileStreamFactory?.() ?? Readable.from([]));
|
|
266
|
+
const tags = args.dataItemOpts?.tags ?? [];
|
|
267
|
+
const item = createData$1(data, this.signer, { tags });
|
|
268
|
+
await item.sign(this.signer);
|
|
269
|
+
const raw = Buffer.from(item.getRaw());
|
|
270
|
+
const localId = item.id || toBase64Url$1(new DataItem$1(raw).id);
|
|
271
|
+
const size = { payloadBytes: data.length, signedBytes: raw.length };
|
|
272
|
+
let autoFundUnavailable;
|
|
273
|
+
let autoFundQuote;
|
|
274
|
+
let cost;
|
|
275
|
+
if (this.autoFund) {
|
|
276
|
+
try {
|
|
277
|
+
autoFundQuote = await quoteHyperbeamUpload({ ...this.quote, signedBytes: raw.length });
|
|
278
|
+
cost = { amount: autoFundQuote.amount, token: "AO" };
|
|
279
|
+
} catch (error) {
|
|
280
|
+
autoFundUnavailable = cleanAutoFundErrorMessage(
|
|
281
|
+
error instanceof Error ? error.message : String(error)
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
try {
|
|
286
|
+
const quote = await quoteHyperbeamUpload({ ...this.quote, signedBytes: raw.length });
|
|
287
|
+
cost = { amount: quote.amount, token: "AO" };
|
|
288
|
+
} catch {
|
|
289
|
+
cost = void 0;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
this.seedPreflight ??= preflightHyperbeamBundlerArBalance(this.uploader);
|
|
293
|
+
await this.seedPreflight;
|
|
294
|
+
if (this.autoFund && autoFundQuote) {
|
|
295
|
+
try {
|
|
296
|
+
await autoFundQuotedHyperbeamLedger({
|
|
297
|
+
...this.autoFund,
|
|
298
|
+
ledgerId: this.autoFund.ledgerId ?? autoFundQuote.ledgerId,
|
|
299
|
+
minimumBalance: this.autoFund.minimumBalance ?? autoFundQuote.amount,
|
|
300
|
+
signedBytes: raw.length,
|
|
301
|
+
tokenId: this.autoFund.tokenId ?? autoFundQuote.tokenId
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
const message = cleanAutoFundErrorMessage(
|
|
305
|
+
error instanceof Error ? error.message : String(error)
|
|
306
|
+
);
|
|
307
|
+
throw new Error(
|
|
308
|
+
[
|
|
309
|
+
`HyperBEAM auto-fund failed: ${message}`,
|
|
310
|
+
`Required upload credit: ${formatAoAmount(cost?.amount ?? autoFundQuote.amount)}`,
|
|
311
|
+
autoFundFailureNote(message),
|
|
312
|
+
await this.paymentHint(false)
|
|
313
|
+
].filter(Boolean).join("\n\n")
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
let posted;
|
|
318
|
+
try {
|
|
319
|
+
posted = await postHyperbeamDataItem(this.uploadUrl, raw, localId);
|
|
320
|
+
} catch (error) {
|
|
321
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
322
|
+
const needsPayment = message.includes("HTTP 402");
|
|
323
|
+
const paymentHint = needsPayment ? await this.paymentHint(autoFundUnavailable ? false : void 0) : void 0;
|
|
324
|
+
throw new Error(
|
|
325
|
+
[
|
|
326
|
+
message,
|
|
327
|
+
needsPayment && autoFundUnavailable ? autoFundUnavailableMessage(autoFundUnavailable) : void 0,
|
|
328
|
+
paymentHint
|
|
329
|
+
].filter(Boolean).join("\n\n")
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return { cost, id: posted.id || localId, size };
|
|
333
|
+
}
|
|
334
|
+
async paymentHint(includeAutoFundInstruction = true) {
|
|
335
|
+
try {
|
|
336
|
+
return hyperbeamAoFundingHint(
|
|
337
|
+
await discoverHyperbeamAoBundlerProfile({ nodeUrl: this.uploader }),
|
|
338
|
+
{ includeAutoFundInstruction }
|
|
339
|
+
);
|
|
340
|
+
} catch {
|
|
341
|
+
try {
|
|
342
|
+
const operator = await fetch(
|
|
343
|
+
`${this.uploader.replace(/\/+$/, "")}/~meta@1.0/info/address`
|
|
344
|
+
).then((res) => res.ok ? res.text() : void 0);
|
|
345
|
+
if (!operator?.trim()) return void 0;
|
|
346
|
+
return [
|
|
347
|
+
"The HyperBEAM node requires AO in its local ledger:",
|
|
348
|
+
`- AO: send funds to ${operator.trim()}. Local ledger: ${HYPERBEAM_DEFAULT_LEDGER_ID} at ${HYPERBEAM_DEFAULT_LEDGER_ROUTE}.`,
|
|
349
|
+
includeAutoFundInstruction ? "Use --hyperbeam-auto-fund to transfer AO and import the credit automatically before upload." : void 0
|
|
350
|
+
].filter(Boolean).join("\n");
|
|
351
|
+
} catch {
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function hyperbeamAoFundingHint(profile, options = {}) {
|
|
358
|
+
const lines = profile.tokens.map((token) => {
|
|
359
|
+
const depositAddress = token.depositAddress ?? profile.node?.operator;
|
|
360
|
+
if (!depositAddress) return;
|
|
361
|
+
const label = token.ticker === "AO" ? "AO" : token.ticker ? `${token.ticker} (${token.id})` : token.id;
|
|
362
|
+
const ledger = token.ledgerId ? profile.ledgers.find((candidate) => candidate.id === token.ledgerId) : void 0;
|
|
363
|
+
const ledgerInfo = ledger ? ` Local ledger: ${ledger.id}${ledger.route ? ` at ${ledger.route}` : ""}.` : "";
|
|
364
|
+
return `- ${label}: send funds to ${depositAddress}.${ledgerInfo}`;
|
|
365
|
+
}).filter(Boolean);
|
|
366
|
+
if (lines.length === 0) return void 0;
|
|
367
|
+
return [
|
|
368
|
+
"The HyperBEAM node requires AO in its local ledger:",
|
|
369
|
+
...lines,
|
|
370
|
+
options.includeAutoFundInstruction === false ? void 0 : "Use --hyperbeam-auto-fund to transfer AO and import the credit automatically before upload."
|
|
371
|
+
].filter(Boolean).join("\n");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const require$1 = createRequire(import.meta.url);
|
|
375
|
+
const { DataItem, createData } = require$1("@dha-team/arbundles");
|
|
376
|
+
async function readableToBuffer(stream) {
|
|
377
|
+
const chunks = [];
|
|
378
|
+
for await (const chunk of stream) {
|
|
379
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
380
|
+
}
|
|
381
|
+
return Buffer.concat(chunks);
|
|
382
|
+
}
|
|
383
|
+
async function streamToBuffer(stream) {
|
|
384
|
+
if (Buffer.isBuffer(stream)) {
|
|
385
|
+
return stream;
|
|
386
|
+
}
|
|
387
|
+
if (stream instanceof Uint8Array) {
|
|
388
|
+
return Buffer.from(stream);
|
|
389
|
+
}
|
|
390
|
+
if (stream instanceof Readable) {
|
|
391
|
+
return readableToBuffer(stream);
|
|
392
|
+
}
|
|
393
|
+
if (stream && typeof stream.getReader === "function") {
|
|
394
|
+
return readableToBuffer(Readable.fromWeb(stream));
|
|
395
|
+
}
|
|
396
|
+
throw new Error("Unsupported upload stream type");
|
|
397
|
+
}
|
|
398
|
+
function toBase64Url(value) {
|
|
399
|
+
if (typeof value === "string") {
|
|
400
|
+
return value;
|
|
401
|
+
}
|
|
402
|
+
return Buffer.from(value).toString("base64url");
|
|
403
|
+
}
|
|
404
|
+
function legacyUploadUrl(base, token) {
|
|
405
|
+
const normalizedBase = base.endsWith("/") ? base : `${base}/`;
|
|
406
|
+
return new URL(`v1/tx/${token}`, normalizedBase).toString();
|
|
407
|
+
}
|
|
408
|
+
function responsePreview(body) {
|
|
409
|
+
const preview = body.replaceAll(/\s+/g, " ").trim();
|
|
410
|
+
if (!preview) {
|
|
411
|
+
return void 0;
|
|
412
|
+
}
|
|
413
|
+
return preview.slice(0, 300);
|
|
414
|
+
}
|
|
415
|
+
class LegacyBundlerClient {
|
|
416
|
+
signer;
|
|
417
|
+
token;
|
|
418
|
+
uploadUrl;
|
|
419
|
+
constructor({ deployKey, sigType, uploader }) {
|
|
420
|
+
const { signer, token } = createSigner(sigType, deployKey);
|
|
421
|
+
this.signer = signer;
|
|
422
|
+
this.token = token;
|
|
423
|
+
this.uploadUrl = legacyUploadUrl(uploader, token);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Sign and post one ANS-104 data item.
|
|
427
|
+
*
|
|
428
|
+
* @param args - Upload payload and data item tags.
|
|
429
|
+
* @returns The upload response with the data item ID.
|
|
430
|
+
*/
|
|
431
|
+
async uploadFile(args) {
|
|
432
|
+
const data = args.file ? typeof args.file === "string" ? fs.readFileSync(args.file) : args.file : await streamToBuffer(args.fileStreamFactory?.() ?? Readable.from([]));
|
|
433
|
+
const tags = args.dataItemOpts?.tags ?? [];
|
|
434
|
+
const item = createData(data, this.signer, { tags });
|
|
435
|
+
await item.sign(this.signer);
|
|
436
|
+
const raw = Buffer.from(item.getRaw());
|
|
437
|
+
const localId = item.id || toBase64Url(new DataItem(raw).id);
|
|
438
|
+
const size = { payloadBytes: data.length, signedBytes: raw.length };
|
|
439
|
+
const response = await fetch(this.uploadUrl, {
|
|
440
|
+
body: raw,
|
|
441
|
+
headers: {
|
|
442
|
+
"content-length": String(raw.length),
|
|
443
|
+
"content-type": "application/octet-stream"
|
|
444
|
+
},
|
|
445
|
+
method: "POST"
|
|
446
|
+
});
|
|
447
|
+
const body = await response.text();
|
|
448
|
+
if (!response.ok) {
|
|
449
|
+
const preview = responsePreview(body);
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Legacy bundler upload failed with HTTP ${response.status}${preview ? `: ${preview}` : ""}`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
let id;
|
|
455
|
+
if (body) {
|
|
456
|
+
try {
|
|
457
|
+
const parsed = JSON.parse(body);
|
|
458
|
+
id = typeof parsed.id === "string" ? parsed.id : void 0;
|
|
459
|
+
} catch {
|
|
460
|
+
id = void 0;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return { id: id ?? localId, size };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function randomizedUploaders(uploaders) {
|
|
468
|
+
const candidates = [...uploaders];
|
|
469
|
+
for (let index = candidates.length - 1; index > 0; index -= 1) {
|
|
470
|
+
const swapIndex = randomInt(index + 1);
|
|
471
|
+
const current = candidates[index];
|
|
472
|
+
const replacement = candidates[swapIndex];
|
|
473
|
+
candidates[index] = replacement;
|
|
474
|
+
candidates[swapIndex] = current;
|
|
475
|
+
}
|
|
476
|
+
return candidates;
|
|
477
|
+
}
|
|
478
|
+
async function discoverUsableHyperbeamUploader(spinner) {
|
|
479
|
+
spinner.start("Discovering active HyperBEAM uploaders");
|
|
480
|
+
const uploaders = randomizedUploaders(await fetchActivePermawebOSBundlers());
|
|
481
|
+
if (uploaders.length === 0) {
|
|
482
|
+
spinner.fail("No active HyperBEAM uploaders found");
|
|
483
|
+
throw new Error("No active HyperBEAM uploaders found");
|
|
484
|
+
}
|
|
485
|
+
const failures = [];
|
|
486
|
+
for (const uploader of uploaders) {
|
|
487
|
+
try {
|
|
488
|
+
await preflightHyperbeamBundlerArBalance(uploader.url);
|
|
489
|
+
spinner.succeed(`Selected HyperBEAM bundler (${chalk.cyan(uploader.url)})`);
|
|
490
|
+
return uploader.url;
|
|
491
|
+
} catch (error) {
|
|
492
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
493
|
+
failures.push(`${uploader.url}: ${message}`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
spinner.fail("No active HyperBEAM uploaders with spendable AR found");
|
|
497
|
+
throw new Error(
|
|
498
|
+
[
|
|
499
|
+
"No active HyperBEAM uploaders with spendable AR found.",
|
|
500
|
+
failures.length > 0 ? failures.join("\n") : void 0
|
|
501
|
+
].filter(Boolean).join("\n")
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
async function runUploadWorkflow(deployKey, config, io) {
|
|
505
|
+
const spinner = ora();
|
|
506
|
+
const uploaderType = config["uploader-type"] ?? "legacy";
|
|
507
|
+
let uploadClient;
|
|
508
|
+
let effectiveUploader;
|
|
509
|
+
if (uploaderType === "hyperbeam") {
|
|
510
|
+
if (config["sig-type"] !== "arweave") {
|
|
511
|
+
io.error("HyperBEAM uploads require --sig-type arweave");
|
|
512
|
+
}
|
|
513
|
+
effectiveUploader = config.uploader ?? await discoverUsableHyperbeamUploader(spinner);
|
|
514
|
+
let autoFund;
|
|
515
|
+
if (config["hyperbeam-auto-fund"]) {
|
|
516
|
+
autoFund = {
|
|
517
|
+
deployKey,
|
|
518
|
+
uploader: effectiveUploader
|
|
519
|
+
};
|
|
520
|
+
if (config["hyperbeam-ao-state-url"]) autoFund.aoStateUrl = config["hyperbeam-ao-state-url"];
|
|
521
|
+
if (config["hyperbeam-ledger-id"]) autoFund.ledgerId = config["hyperbeam-ledger-id"];
|
|
522
|
+
if (config["hyperbeam-token-id"]) autoFund.tokenId = config["hyperbeam-token-id"];
|
|
523
|
+
if (config["hyperbeam-fund-amount"]) {
|
|
524
|
+
autoFund.minimumBalance = parseHyperbeamFundAmount(config["hyperbeam-fund-amount"]);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
spinner.start("Initializing HyperBEAM bundler");
|
|
528
|
+
uploadClient = new HyperbeamBundlerClient({
|
|
529
|
+
autoFund,
|
|
530
|
+
deployKey,
|
|
531
|
+
quote: {
|
|
532
|
+
ledgerId: config["hyperbeam-ledger-id"],
|
|
533
|
+
tokenId: config["hyperbeam-token-id"],
|
|
534
|
+
uploader: effectiveUploader
|
|
535
|
+
},
|
|
536
|
+
uploadPath: config["hyperbeam-upload-path"] ?? "/~bundler@1.0/item?codec-device=ans104@1.0",
|
|
537
|
+
uploader: effectiveUploader
|
|
538
|
+
});
|
|
539
|
+
spinner.succeed(`HyperBEAM bundler initialized (${chalk.cyan(effectiveUploader)})`);
|
|
540
|
+
} else {
|
|
541
|
+
effectiveUploader = config.uploader ?? "https://up.arweave.net";
|
|
542
|
+
spinner.start("Initializing legacy bundler");
|
|
543
|
+
uploadClient = new LegacyBundlerClient({
|
|
544
|
+
deployKey,
|
|
545
|
+
sigType: config["sig-type"],
|
|
546
|
+
uploader: effectiveUploader
|
|
547
|
+
});
|
|
548
|
+
spinner.succeed("Legacy bundler initialized");
|
|
549
|
+
}
|
|
550
|
+
let txOrManifestId;
|
|
551
|
+
let cost;
|
|
552
|
+
let size;
|
|
553
|
+
try {
|
|
554
|
+
if (config["deploy-file"]) {
|
|
555
|
+
const filePath = expandPath(config["deploy-file"]);
|
|
556
|
+
spinner.start(`Uploading file ${chalk.yellow(config["deploy-file"])}`);
|
|
557
|
+
let cache = config["dedupe-cache-max-entries"] > 0 ? loadCache() : {};
|
|
558
|
+
const uploadResult = await uploadFile(uploadClient, filePath, { cache });
|
|
559
|
+
if (!uploadResult.transactionId) {
|
|
560
|
+
spinner.fail("File upload failed: no transaction ID returned");
|
|
561
|
+
io.error("File upload failed: no transaction ID returned");
|
|
562
|
+
}
|
|
563
|
+
txOrManifestId = uploadResult.transactionId;
|
|
564
|
+
cost = uploadResult.cost;
|
|
565
|
+
size = uploadResult.size;
|
|
566
|
+
if (uploadResult.updatedCache && config["dedupe-cache-max-entries"] > 0) {
|
|
567
|
+
cache = cleanupCache(uploadResult.updatedCache, config["dedupe-cache-max-entries"]);
|
|
568
|
+
saveCache(cache);
|
|
569
|
+
}
|
|
570
|
+
if (uploadResult.cacheHit) {
|
|
571
|
+
spinner.succeed(`File cache hit - reusing transaction ${chalk.green(txOrManifestId)}`);
|
|
572
|
+
} else {
|
|
573
|
+
const cacheMsg = config["dedupe-cache-max-entries"] > 0 ? chalk.gray("(cached for future uploads)") : "";
|
|
574
|
+
spinner.succeed(`File uploaded: ${chalk.green(txOrManifestId)} ${cacheMsg}`.trim());
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
const folderPath = expandPath(config["deploy-folder"]);
|
|
578
|
+
spinner.start(`Uploading folder ${chalk.yellow(config["deploy-folder"])}`);
|
|
579
|
+
let cache = config["dedupe-cache-max-entries"] > 0 ? loadCache() : {};
|
|
580
|
+
const uploadResult = await uploadFolder(uploadClient, folderPath, {
|
|
581
|
+
cache,
|
|
582
|
+
concurrency: config["hyperbeam-auto-fund"] ? 1 : void 0,
|
|
583
|
+
omitManifestDeviceTag: uploaderType === "hyperbeam",
|
|
584
|
+
throwOnFailure: true
|
|
585
|
+
});
|
|
586
|
+
if (!uploadResult.transactionId) {
|
|
587
|
+
spinner.fail("Folder upload failed: no transaction ID returned");
|
|
588
|
+
io.error("Folder upload failed: no transaction ID returned");
|
|
589
|
+
}
|
|
590
|
+
txOrManifestId = uploadResult.transactionId;
|
|
591
|
+
cost = uploadResult.cost;
|
|
592
|
+
size = uploadResult.size;
|
|
593
|
+
if (uploadResult.updatedCache && config["dedupe-cache-max-entries"] > 0) {
|
|
594
|
+
cache = cleanupCache(uploadResult.updatedCache, config["dedupe-cache-max-entries"]);
|
|
595
|
+
saveCache(cache);
|
|
596
|
+
}
|
|
597
|
+
const { cacheHits, totalFiles, uploaded } = uploadResult;
|
|
598
|
+
const statsMsg = cacheHits > 0 ? chalk.gray(` (${cacheHits}/${totalFiles} files cached, ${uploaded} uploaded)`) : "";
|
|
599
|
+
if (uploadResult.cacheHit) {
|
|
600
|
+
spinner.succeed(`All ${totalFiles} files cached - manifest: ${chalk.green(txOrManifestId)}`);
|
|
601
|
+
} else {
|
|
602
|
+
const cacheMsg = config["dedupe-cache-max-entries"] > 0 ? chalk.gray(" (files cached for future uploads)") : "";
|
|
603
|
+
spinner.succeed(`Folder uploaded: ${chalk.green(txOrManifestId)}${statsMsg}${cacheMsg}`);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} catch (uploadError) {
|
|
607
|
+
spinner.fail("Upload failed");
|
|
608
|
+
const errorMessage = uploadError instanceof Error ? uploadError.message : String(uploadError);
|
|
609
|
+
io.error(`Upload failed: ${errorMessage}`);
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
cost,
|
|
613
|
+
size,
|
|
614
|
+
transactionId: txOrManifestId,
|
|
615
|
+
uploader: effectiveUploader
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export { hyperbeamBundlerLink as h, runUploadWorkflow as r };
|
|
620
|
+
//# sourceMappingURL=upload-workflow-f3hExxtV.js.map
|