permaweb-deploy 3.0.1 → 3.2.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 CHANGED
@@ -190,6 +190,45 @@ permaweb-deploy deploy --arns-name my-app --sig-type ethereum --private-key "0x.
190
190
  - `--private-key, -k`: Private key or JWK JSON string (alternative to `--wallet`)
191
191
  - `--on-demand`: Enable on-demand payment with specified token. Choices: `ario`, `base-eth`
192
192
  - `--max-token-amount`: Maximum token amount for on-demand payment (used with `--on-demand`)
193
+ - `--no-dedupe`: Disable deduplication (do not cache or reuse previous uploads)
194
+ - `--dedupe-cache-max-entries`: Maximum number of entries to keep in the dedupe cache (LRU). Default: `10000`
195
+
196
+ ### Deduplication
197
+
198
+ By default, permaweb-deploy caches your deployment log to prevent uploading duplicate (unchanged) files. This saves both time and upload costs by reusing existing data on Arweave.
199
+
200
+ **How it works:**
201
+
202
+ 1. When you deploy, permaweb-deploy hashes each file in your build
203
+ 2. It checks the local cache for matching hashes from previous uploads
204
+ 3. Files that haven't changed are skipped - the existing transaction ID is reused
205
+ 4. Only new or modified files are uploaded to Arweave
206
+ 5. The cache is stored locally in `.permaweb-deploy/transaction-cache.json`
207
+
208
+ **Disable deduplication:**
209
+
210
+ If you need to force a fresh upload of all files (e.g., for debugging or to ensure a completely new deployment):
211
+
212
+ ```bash
213
+ permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --no-dedupe
214
+ ```
215
+
216
+ **Limit cache size:**
217
+
218
+ The dedupe cache uses an LRU (Least Recently Used) eviction strategy. By default, it keeps up to 10,000 entries. You can adjust this limit:
219
+
220
+ ```bash
221
+ # Keep only the last 1000 file entries
222
+ permaweb-deploy deploy --arns-name my-app --wallet ./wallet.json --dedupe-cache-max-entries 1000
223
+ ```
224
+
225
+ **Cache location:**
226
+
227
+ The cache file is stored at `.permaweb-deploy/transaction-cache.json` in your project root. You can:
228
+
229
+ - Add it to `.gitignore` if you don't want to share cache across team members
230
+ - Commit it to share cached transaction IDs with your team (reduces duplicate uploads)
231
+ - Delete it to start fresh: `rm -rf .permaweb-deploy/`
193
232
 
194
233
  ### Package.json Scripts
195
234
 
@@ -326,6 +365,32 @@ jobs:
326
365
  max-token-amount: '2.0'
327
366
  ```
328
367
 
368
+ ### Disabling Deduplication
369
+
370
+ By default, the action caches transaction IDs to avoid re-uploading unchanged files. To disable this:
371
+
372
+ ```yaml
373
+ - name: Deploy without dedupe
374
+ uses: permaweb/permaweb-deploy@v1
375
+ with:
376
+ deploy-key: ${{ secrets.DEPLOY_KEY }}
377
+ arns-name: myapp
378
+ deploy-folder: ./dist
379
+ no-dedupe: 'true'
380
+ ```
381
+
382
+ You can also limit the cache size:
383
+
384
+ ```yaml
385
+ - name: Deploy with limited cache
386
+ uses: permaweb/permaweb-deploy@v1
387
+ with:
388
+ deploy-key: ${{ secrets.DEPLOY_KEY }}
389
+ arns-name: myapp
390
+ deploy-folder: ./dist
391
+ dedupe-cache-max-entries: '1000'
392
+ ```
393
+
329
394
  ---
330
395
 
331
396
  ## CLI in GitHub Actions
@@ -0,0 +1,444 @@
1
+ import path from 'node:path';
2
+ import { Readable } from 'node:stream';
3
+ import * as mime from 'mime-types';
4
+ import crypto from 'node:crypto';
5
+ import fs from 'node:fs';
6
+ import { C as CACHE_DIR, a as CACHE_FILE } from './cache-BssFyqB-.js';
7
+
8
+ function getCachePath() {
9
+ return path.join(process.cwd(), CACHE_DIR, CACHE_FILE);
10
+ }
11
+ function loadCache() {
12
+ const cachePath = getCachePath();
13
+ try {
14
+ if (!fs.existsSync(cachePath)) {
15
+ return {};
16
+ }
17
+ const content = fs.readFileSync(cachePath, "utf8");
18
+ return JSON.parse(content);
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+ function saveCache(cache) {
24
+ const cachePath = getCachePath();
25
+ const cacheDir = path.dirname(cachePath);
26
+ if (!fs.existsSync(cacheDir)) {
27
+ fs.mkdirSync(cacheDir, { recursive: true });
28
+ }
29
+ fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), "utf8");
30
+ }
31
+ async function hashFile(filePath) {
32
+ return new Promise((resolve, reject) => {
33
+ const hash = crypto.createHash("sha256");
34
+ const stream = fs.createReadStream(filePath);
35
+ stream.on("data", (chunk) => hash.update(chunk));
36
+ stream.on("end", () => resolve(hash.digest("hex")));
37
+ stream.on("error", reject);
38
+ });
39
+ }
40
+ function getAllFiles(dirPath, basePath = dirPath) {
41
+ const files = [];
42
+ for (const item of fs.readdirSync(dirPath)) {
43
+ const fullPath = path.join(dirPath, item);
44
+ const stats = fs.statSync(fullPath);
45
+ if (stats.isDirectory()) {
46
+ files.push(...getAllFiles(fullPath, basePath));
47
+ } else {
48
+ files.push(path.relative(basePath, fullPath));
49
+ }
50
+ }
51
+ return files;
52
+ }
53
+ function getCachedTransaction(cache, hash) {
54
+ return cache[hash];
55
+ }
56
+ function setCachedTransaction(cache, hash, transactionId) {
57
+ const now = Date.now();
58
+ const existing = cache[hash];
59
+ return {
60
+ ...cache,
61
+ [hash]: {
62
+ createdAtTimestamp: existing?.createdAtTimestamp ?? now,
63
+ lastUsedTimestamp: now,
64
+ transactionId
65
+ }
66
+ };
67
+ }
68
+ function touchCacheEntry(cache, hash) {
69
+ const existing = cache[hash];
70
+ if (!existing) {
71
+ return cache;
72
+ }
73
+ return {
74
+ ...cache,
75
+ [hash]: {
76
+ ...existing,
77
+ lastUsedTimestamp: Date.now()
78
+ }
79
+ };
80
+ }
81
+ function cleanupCache(cache, maxEntries) {
82
+ const entries = Object.entries(cache);
83
+ if (entries.length <= maxEntries) {
84
+ return cache;
85
+ }
86
+ const sorted = entries.sort(([, a], [, b]) => b.lastUsedTimestamp - a.lastUsedTimestamp);
87
+ const kept = sorted.slice(0, maxEntries);
88
+ return Object.fromEntries(kept);
89
+ }
90
+
91
+ /*
92
+ How it works:
93
+ `this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.
94
+ */
95
+
96
+ class Node {
97
+ value;
98
+ next;
99
+
100
+ constructor(value) {
101
+ this.value = value;
102
+ }
103
+ }
104
+
105
+ class Queue {
106
+ #head;
107
+ #tail;
108
+ #size;
109
+
110
+ constructor() {
111
+ this.clear();
112
+ }
113
+
114
+ enqueue(value) {
115
+ const node = new Node(value);
116
+
117
+ if (this.#head) {
118
+ this.#tail.next = node;
119
+ this.#tail = node;
120
+ } else {
121
+ this.#head = node;
122
+ this.#tail = node;
123
+ }
124
+
125
+ this.#size++;
126
+ }
127
+
128
+ dequeue() {
129
+ const current = this.#head;
130
+ if (!current) {
131
+ return;
132
+ }
133
+
134
+ this.#head = this.#head.next;
135
+ this.#size--;
136
+
137
+ // Clean up tail reference when queue becomes empty
138
+ if (!this.#head) {
139
+ this.#tail = undefined;
140
+ }
141
+
142
+ return current.value;
143
+ }
144
+
145
+ peek() {
146
+ if (!this.#head) {
147
+ return;
148
+ }
149
+
150
+ return this.#head.value;
151
+
152
+ // TODO: Node.js 18.
153
+ // return this.#head?.value;
154
+ }
155
+
156
+ clear() {
157
+ this.#head = undefined;
158
+ this.#tail = undefined;
159
+ this.#size = 0;
160
+ }
161
+
162
+ get size() {
163
+ return this.#size;
164
+ }
165
+
166
+ * [Symbol.iterator]() {
167
+ let current = this.#head;
168
+
169
+ while (current) {
170
+ yield current.value;
171
+ current = current.next;
172
+ }
173
+ }
174
+
175
+ * drain() {
176
+ while (this.#head) {
177
+ yield this.dequeue();
178
+ }
179
+ }
180
+ }
181
+
182
+ function pLimit(concurrency) {
183
+ validateConcurrency(concurrency);
184
+
185
+ const queue = new Queue();
186
+ let activeCount = 0;
187
+
188
+ const resumeNext = () => {
189
+ // Process the next queued function if we're under the concurrency limit
190
+ if (activeCount < concurrency && queue.size > 0) {
191
+ activeCount++;
192
+ queue.dequeue()();
193
+ }
194
+ };
195
+
196
+ const next = () => {
197
+ activeCount--;
198
+ resumeNext();
199
+ };
200
+
201
+ const run = async (function_, resolve, arguments_) => {
202
+ // Execute the function and capture the result promise
203
+ const result = (async () => function_(...arguments_))();
204
+
205
+ // Resolve immediately with the promise (don't wait for completion)
206
+ resolve(result);
207
+
208
+ // Wait for the function to complete (success or failure)
209
+ // We catch errors here to prevent unhandled rejections,
210
+ // but the original promise rejection is preserved for the caller
211
+ try {
212
+ await result;
213
+ } catch {}
214
+
215
+ // Decrement active count and process next queued function
216
+ next();
217
+ };
218
+
219
+ const enqueue = (function_, resolve, arguments_) => {
220
+ // Queue the internal resolve function instead of the run function
221
+ // to preserve the asynchronous execution context.
222
+ new Promise(internalResolve => { // eslint-disable-line promise/param-names
223
+ queue.enqueue(internalResolve);
224
+ }).then(run.bind(undefined, function_, resolve, arguments_)); // eslint-disable-line promise/prefer-await-to-then
225
+
226
+ // Start processing immediately if we haven't reached the concurrency limit
227
+ if (activeCount < concurrency) {
228
+ resumeNext();
229
+ }
230
+ };
231
+
232
+ const generator = (function_, ...arguments_) => new Promise(resolve => {
233
+ enqueue(function_, resolve, arguments_);
234
+ });
235
+
236
+ Object.defineProperties(generator, {
237
+ activeCount: {
238
+ get: () => activeCount,
239
+ },
240
+ pendingCount: {
241
+ get: () => queue.size,
242
+ },
243
+ clearQueue: {
244
+ value() {
245
+ queue.clear();
246
+ },
247
+ },
248
+ concurrency: {
249
+ get: () => concurrency,
250
+
251
+ set(newConcurrency) {
252
+ validateConcurrency(newConcurrency);
253
+ concurrency = newConcurrency;
254
+
255
+ queueMicrotask(() => {
256
+ // eslint-disable-next-line no-unmodified-loop-condition
257
+ while (activeCount < concurrency && queue.size > 0) {
258
+ resumeNext();
259
+ }
260
+ });
261
+ },
262
+ },
263
+ map: {
264
+ async value(iterable, function_) {
265
+ const promises = Array.from(iterable, (value, index) => this(function_, value, index));
266
+ return Promise.all(promises);
267
+ },
268
+ },
269
+ });
270
+
271
+ return generator;
272
+ }
273
+
274
+ function validateConcurrency(concurrency) {
275
+ if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
276
+ throw new TypeError('Expected `concurrency` to be a number from 1 and up');
277
+ }
278
+ }
279
+
280
+ async function uploadFile(turbo, filePath, options) {
281
+ const mimeType = mime.lookup(filePath) || "application/octet-stream";
282
+ const fileHash = options?.cache ? await hashFile(filePath) : void 0;
283
+ if (fileHash && options?.cache) {
284
+ const cached = getCachedTransaction(options.cache, fileHash);
285
+ if (cached) {
286
+ const updatedCache = touchCacheEntry(options.cache, fileHash);
287
+ return {
288
+ cacheHit: true,
289
+ transactionId: cached.transactionId,
290
+ updatedCache
291
+ };
292
+ }
293
+ }
294
+ const uploadResult = await turbo.uploadFile({
295
+ dataItemOpts: {
296
+ tags: [
297
+ {
298
+ name: "App-Name",
299
+ value: "Permaweb-Deploy"
300
+ },
301
+ {
302
+ name: "anchor",
303
+ value: (/* @__PURE__ */ new Date()).toISOString()
304
+ },
305
+ {
306
+ name: "Content-Type",
307
+ value: mimeType
308
+ }
309
+ ]
310
+ },
311
+ file: filePath,
312
+ ...options?.fundingMode && { fundingMode: options.fundingMode }
313
+ });
314
+ if (!uploadResult?.id) {
315
+ throw new Error("Failed to upload file: upload result missing transaction ID");
316
+ }
317
+ if (fileHash && options?.cache) {
318
+ const updatedCache = setCachedTransaction(options.cache, fileHash, uploadResult.id);
319
+ return {
320
+ cacheHit: false,
321
+ transactionId: uploadResult.id,
322
+ updatedCache
323
+ };
324
+ }
325
+ return {
326
+ cacheHit: false,
327
+ transactionId: uploadResult.id
328
+ };
329
+ }
330
+ const DEFAULT_UPLOAD_CONCURRENCY = 10;
331
+ async function uploadFolder(turbo, folderPath, options) {
332
+ const concurrency = options?.concurrency ?? DEFAULT_UPLOAD_CONCURRENCY;
333
+ const useCache = options?.cache !== void 0;
334
+ const relativePaths = getAllFiles(folderPath);
335
+ if (relativePaths.length === 0) {
336
+ throw new Error("Folder is empty, nothing to upload");
337
+ }
338
+ const tasks = await Promise.all(
339
+ relativePaths.map(async (relativePath) => {
340
+ const fullPath = path.join(folderPath, relativePath);
341
+ const hash = useCache ? await hashFile(fullPath) : "";
342
+ return { fullPath, hash, relativePath };
343
+ })
344
+ );
345
+ let cache = options?.cache ?? {};
346
+ let cacheHits = 0;
347
+ for (const task of tasks) {
348
+ if (useCache && task.hash) {
349
+ const cached = getCachedTransaction(cache, task.hash);
350
+ if (cached) {
351
+ task.cached = { transactionId: cached.transactionId };
352
+ cache = touchCacheEntry(cache, task.hash);
353
+ cacheHits++;
354
+ }
355
+ }
356
+ }
357
+ const uncachedTasks = tasks.filter((t) => !t.cached);
358
+ const limit = pLimit(concurrency);
359
+ const uploadResults = await Promise.all(
360
+ uncachedTasks.map(
361
+ (task) => limit(async () => {
362
+ const mimeType = mime.lookup(task.fullPath) || "application/octet-stream";
363
+ const uploadResult = await turbo.uploadFile({
364
+ dataItemOpts: {
365
+ tags: [
366
+ { name: "App-Name", value: "Permaweb-Deploy" },
367
+ { name: "Content-Type", value: mimeType }
368
+ ]
369
+ },
370
+ file: task.fullPath,
371
+ ...options?.fundingMode && { fundingMode: options.fundingMode }
372
+ });
373
+ if (!uploadResult?.id) {
374
+ if (options?.throwOnFailure) {
375
+ throw new Error(`Failed to upload file: ${task.relativePath}`);
376
+ }
377
+ return { hash: task.hash, task, transactionId: null };
378
+ }
379
+ return { hash: task.hash, task, transactionId: uploadResult.id };
380
+ })
381
+ )
382
+ );
383
+ for (const result of uploadResults) {
384
+ if (useCache && result.hash && result.transactionId) {
385
+ cache = setCachedTransaction(cache, result.hash, result.transactionId);
386
+ }
387
+ }
388
+ const failedUploads = uploadResults.filter((r) => r.transactionId === null);
389
+ if (failedUploads.length > 0 && options?.throwOnFailure) {
390
+ throw new Error(
391
+ `Failed to upload ${failedUploads.length} file(s): ${failedUploads.map((f) => f.task.relativePath).join(", ")}`
392
+ );
393
+ }
394
+ const manifestPaths = {};
395
+ for (const task of tasks) {
396
+ let transactionId = null;
397
+ if (task.cached) {
398
+ transactionId = task.cached.transactionId;
399
+ } else {
400
+ const uploadResult = uploadResults.find((r) => r.task === task);
401
+ transactionId = uploadResult?.transactionId ?? null;
402
+ }
403
+ if (transactionId) {
404
+ manifestPaths[task.relativePath] = { id: transactionId };
405
+ if (task.relativePath.endsWith("/index.html")) {
406
+ const dirPath = task.relativePath.replace(/\/index\.html$/, "");
407
+ manifestPaths[dirPath] = { id: transactionId };
408
+ }
409
+ }
410
+ }
411
+ const indexPath = relativePaths.includes("index.html") ? "index.html" : void 0;
412
+ const manifest = {
413
+ manifest: "arweave/paths",
414
+ version: "0.2.0",
415
+ ...indexPath && { index: { path: indexPath } },
416
+ paths: manifestPaths
417
+ };
418
+ const manifestBuffer = Buffer.from(JSON.stringify(manifest));
419
+ const manifestUploadResult = await turbo.uploadFile({
420
+ dataItemOpts: {
421
+ tags: [
422
+ { name: "App-Name", value: "Permaweb-Deploy" },
423
+ { name: "Content-Type", value: "application/x.arweave-manifest+json" }
424
+ ]
425
+ },
426
+ fileSizeFactory: () => manifestBuffer.length,
427
+ fileStreamFactory: () => Readable.from(manifestBuffer),
428
+ ...options?.fundingMode && { fundingMode: options.fundingMode }
429
+ });
430
+ if (!manifestUploadResult?.id) {
431
+ throw new Error("Failed to upload manifest: upload result missing transaction ID");
432
+ }
433
+ return {
434
+ cacheHit: cacheHits === tasks.length,
435
+ cacheHits,
436
+ totalFiles: tasks.length,
437
+ transactionId: manifestUploadResult.id,
438
+ updatedCache: useCache ? cache : void 0,
439
+ uploaded: uncachedTasks.length - failedUploads.length
440
+ };
441
+ }
442
+
443
+ export { uploadFolder as a, cleanupCache as c, loadCache as l, saveCache as s, uploadFile as u };
444
+ //# sourceMappingURL=uploader-d40K7e4Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploader-d40K7e4Z.js","sources":["../../src/utils/cache.ts","../../node_modules/.pnpm/yocto-queue@1.2.2/node_modules/yocto-queue/index.js","../../node_modules/.pnpm/p-limit@7.2.0/node_modules/p-limit/index.js","../../src/utils/uploader.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nimport { CACHE_DIR, CACHE_FILE } from '../constants/cache.js'\n\nexport interface TransactionCacheEntry {\n createdAtTimestamp: number\n lastUsedTimestamp: number\n transactionId: string\n}\n\nexport type TransactionCache = Record<string, TransactionCacheEntry>\n\n/**\n * Get the path to the cache file in the current working directory\n */\nexport function getCachePath(): string {\n return path.join(process.cwd(), CACHE_DIR, CACHE_FILE)\n}\n\n/**\n * Load the transaction cache from disk\n * Returns an empty object if the cache file doesn't exist or is invalid\n */\nexport function loadCache(): TransactionCache {\n const cachePath = getCachePath()\n\n try {\n if (!fs.existsSync(cachePath)) {\n return {}\n }\n\n const content = fs.readFileSync(cachePath, 'utf8')\n return JSON.parse(content) as TransactionCache\n } catch {\n // If the cache is corrupted or unreadable, start fresh\n return {}\n }\n}\n\n/**\n * Save the transaction cache to disk\n * Creates the cache directory if it doesn't exist\n */\nexport function saveCache(cache: TransactionCache): void {\n const cachePath = getCachePath()\n const cacheDir = path.dirname(cachePath)\n\n if (!fs.existsSync(cacheDir)) {\n fs.mkdirSync(cacheDir, { recursive: true })\n }\n\n fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2), 'utf8')\n}\n\n/**\n * Compute the SHA-256 hash of a file using streaming\n */\nexport async function hashFile(filePath: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const hash = crypto.createHash('sha256')\n const stream = fs.createReadStream(filePath)\n\n stream.on('data', (chunk) => hash.update(chunk))\n stream.on('end', () => resolve(hash.digest('hex')))\n stream.on('error', reject)\n })\n}\n\n/**\n * Recursively get all files in a directory\n * Returns relative paths from the base directory\n */\nexport function getAllFiles(dirPath: string, basePath: string = dirPath): string[] {\n const files: string[] = []\n\n for (const item of fs.readdirSync(dirPath)) {\n const fullPath = path.join(dirPath, item)\n const stats = fs.statSync(fullPath)\n\n if (stats.isDirectory()) {\n files.push(...getAllFiles(fullPath, basePath))\n } else {\n // Store relative path for consistent hashing\n files.push(path.relative(basePath, fullPath))\n }\n }\n\n return files\n}\n\n/**\n * Get a cached transaction entry by its file hash\n */\nexport function getCachedTransaction(\n cache: TransactionCache,\n hash: string,\n): TransactionCacheEntry | undefined {\n return cache[hash]\n}\n\n/**\n * Add or update a cache entry for a file hash\n * Updates lastUsedTimestamp if the entry already exists\n */\nexport function setCachedTransaction(\n cache: TransactionCache,\n hash: string,\n transactionId: string,\n): TransactionCache {\n const now = Date.now()\n const existing = cache[hash]\n\n return {\n ...cache,\n [hash]: {\n createdAtTimestamp: existing?.createdAtTimestamp ?? now,\n lastUsedTimestamp: now,\n transactionId,\n },\n }\n}\n\n/**\n * Update the lastUsedTimestamp for an existing cache entry\n */\nexport function touchCacheEntry(cache: TransactionCache, hash: string): TransactionCache {\n const existing = cache[hash]\n if (!existing) {\n return cache\n }\n\n return {\n ...cache,\n [hash]: {\n ...existing,\n lastUsedTimestamp: Date.now(),\n },\n }\n}\n\n/**\n * Clean up the cache by keeping only the most recently used entries\n * Entries are sorted by lastUsedTimestamp descending, keeping the newest maxEntries\n */\nexport function cleanupCache(cache: TransactionCache, maxEntries: number): TransactionCache {\n const entries = Object.entries(cache)\n\n if (entries.length <= maxEntries) {\n return cache\n }\n\n // Sort by lastUsedTimestamp descending (newest first)\n const sorted = entries.sort(([, a], [, b]) => b.lastUsedTimestamp - a.lastUsedTimestamp)\n\n // Keep only the newest maxEntries\n const kept = sorted.slice(0, maxEntries)\n\n return Object.fromEntries(kept)\n}\n","/*\nHow it works:\n`this.#head` is an instance of `Node` which keeps track of its current value and nests another instance of `Node` that keeps the value that comes after it. When a value is provided to `.enqueue()`, the code needs to iterate through `this.#head`, going deeper and deeper to find the last value. However, iterating through every single item is slow. This problem is solved by saving a reference to the last value as `this.#tail` so that it can reference it to add a new value.\n*/\n\nclass Node {\n\tvalue;\n\tnext;\n\n\tconstructor(value) {\n\t\tthis.value = value;\n\t}\n}\n\nexport default class Queue {\n\t#head;\n\t#tail;\n\t#size;\n\n\tconstructor() {\n\t\tthis.clear();\n\t}\n\n\tenqueue(value) {\n\t\tconst node = new Node(value);\n\n\t\tif (this.#head) {\n\t\t\tthis.#tail.next = node;\n\t\t\tthis.#tail = node;\n\t\t} else {\n\t\t\tthis.#head = node;\n\t\t\tthis.#tail = node;\n\t\t}\n\n\t\tthis.#size++;\n\t}\n\n\tdequeue() {\n\t\tconst current = this.#head;\n\t\tif (!current) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#head = this.#head.next;\n\t\tthis.#size--;\n\n\t\t// Clean up tail reference when queue becomes empty\n\t\tif (!this.#head) {\n\t\t\tthis.#tail = undefined;\n\t\t}\n\n\t\treturn current.value;\n\t}\n\n\tpeek() {\n\t\tif (!this.#head) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn this.#head.value;\n\n\t\t// TODO: Node.js 18.\n\t\t// return this.#head?.value;\n\t}\n\n\tclear() {\n\t\tthis.#head = undefined;\n\t\tthis.#tail = undefined;\n\t\tthis.#size = 0;\n\t}\n\n\tget size() {\n\t\treturn this.#size;\n\t}\n\n\t* [Symbol.iterator]() {\n\t\tlet current = this.#head;\n\n\t\twhile (current) {\n\t\t\tyield current.value;\n\t\t\tcurrent = current.next;\n\t\t}\n\t}\n\n\t* drain() {\n\t\twhile (this.#head) {\n\t\t\tyield this.dequeue();\n\t\t}\n\t}\n}\n","import Queue from 'yocto-queue';\n\nexport default function pLimit(concurrency) {\n\tvalidateConcurrency(concurrency);\n\n\tconst queue = new Queue();\n\tlet activeCount = 0;\n\n\tconst resumeNext = () => {\n\t\t// Process the next queued function if we're under the concurrency limit\n\t\tif (activeCount < concurrency && queue.size > 0) {\n\t\t\tactiveCount++;\n\t\t\tqueue.dequeue()();\n\t\t}\n\t};\n\n\tconst next = () => {\n\t\tactiveCount--;\n\t\tresumeNext();\n\t};\n\n\tconst run = async (function_, resolve, arguments_) => {\n\t\t// Execute the function and capture the result promise\n\t\tconst result = (async () => function_(...arguments_))();\n\n\t\t// Resolve immediately with the promise (don't wait for completion)\n\t\tresolve(result);\n\n\t\t// Wait for the function to complete (success or failure)\n\t\t// We catch errors here to prevent unhandled rejections,\n\t\t// but the original promise rejection is preserved for the caller\n\t\ttry {\n\t\t\tawait result;\n\t\t} catch {}\n\n\t\t// Decrement active count and process next queued function\n\t\tnext();\n\t};\n\n\tconst enqueue = (function_, resolve, arguments_) => {\n\t\t// Queue the internal resolve function instead of the run function\n\t\t// to preserve the asynchronous execution context.\n\t\tnew Promise(internalResolve => { // eslint-disable-line promise/param-names\n\t\t\tqueue.enqueue(internalResolve);\n\t\t}).then(run.bind(undefined, function_, resolve, arguments_)); // eslint-disable-line promise/prefer-await-to-then\n\n\t\t// Start processing immediately if we haven't reached the concurrency limit\n\t\tif (activeCount < concurrency) {\n\t\t\tresumeNext();\n\t\t}\n\t};\n\n\tconst generator = (function_, ...arguments_) => new Promise(resolve => {\n\t\tenqueue(function_, resolve, arguments_);\n\t});\n\n\tObject.defineProperties(generator, {\n\t\tactiveCount: {\n\t\t\tget: () => activeCount,\n\t\t},\n\t\tpendingCount: {\n\t\t\tget: () => queue.size,\n\t\t},\n\t\tclearQueue: {\n\t\t\tvalue() {\n\t\t\t\tqueue.clear();\n\t\t\t},\n\t\t},\n\t\tconcurrency: {\n\t\t\tget: () => concurrency,\n\n\t\t\tset(newConcurrency) {\n\t\t\t\tvalidateConcurrency(newConcurrency);\n\t\t\t\tconcurrency = newConcurrency;\n\n\t\t\t\tqueueMicrotask(() => {\n\t\t\t\t\t// eslint-disable-next-line no-unmodified-loop-condition\n\t\t\t\t\twhile (activeCount < concurrency && queue.size > 0) {\n\t\t\t\t\t\tresumeNext();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t\tmap: {\n\t\t\tasync value(iterable, function_) {\n\t\t\t\tconst promises = Array.from(iterable, (value, index) => this(function_, value, index));\n\t\t\t\treturn Promise.all(promises);\n\t\t\t},\n\t\t},\n\t});\n\n\treturn generator;\n}\n\nexport function limitFunction(function_, options) {\n\tconst {concurrency} = options;\n\tconst limit = pLimit(concurrency);\n\n\treturn (...arguments_) => limit(() => function_(...arguments_));\n}\n\nfunction validateConcurrency(concurrency) {\n\tif (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {\n\t\tthrow new TypeError('Expected `concurrency` to be a number from 1 and up');\n\t}\n}\n","import path from 'node:path'\nimport { Readable } from 'node:stream'\n\nimport { OnDemandFunding, type TurboAuthenticatedClient } from '@ardrive/turbo-sdk'\nimport * as mime from 'mime-types'\nimport pLimit from 'p-limit'\n\nimport {\n getAllFiles,\n getCachedTransaction,\n hashFile,\n setCachedTransaction,\n touchCacheEntry,\n type TransactionCache,\n} from './cache.js'\n\nexport interface UploadResult {\n cacheHit: boolean\n transactionId: string\n updatedCache?: TransactionCache\n}\n\nexport interface FolderUploadResult extends UploadResult {\n /** Number of files that were cache hits (not re-uploaded) */\n cacheHits: number\n /** Total number of files in the folder */\n totalFiles: number\n /** Number of files that were uploaded */\n uploaded: number\n}\n\nexport async function uploadFile(\n turbo: TurboAuthenticatedClient,\n filePath: string,\n options?: {\n cache?: TransactionCache\n fundingMode?: OnDemandFunding\n },\n): Promise<UploadResult> {\n const mimeType = mime.lookup(filePath) || 'application/octet-stream'\n\n // Compute hash if cache is provided\n const fileHash = options?.cache ? await hashFile(filePath) : undefined\n\n // Check cache for hit\n if (fileHash && options?.cache) {\n const cached = getCachedTransaction(options.cache, fileHash)\n if (cached) {\n const updatedCache = touchCacheEntry(options.cache, fileHash)\n return {\n cacheHit: true,\n transactionId: cached.transactionId,\n updatedCache,\n }\n }\n }\n\n // Upload file\n const uploadResult = await turbo.uploadFile({\n dataItemOpts: {\n tags: [\n {\n name: 'App-Name',\n value: 'Permaweb-Deploy',\n },\n {\n name: 'anchor',\n value: new Date().toISOString(),\n },\n {\n name: 'Content-Type',\n value: mimeType,\n },\n ],\n },\n file: filePath,\n ...(options?.fundingMode && { fundingMode: options.fundingMode }),\n })\n\n if (!uploadResult?.id) {\n throw new Error('Failed to upload file: upload result missing transaction ID')\n }\n\n // Store in cache if provided\n if (fileHash && options?.cache) {\n const updatedCache = setCachedTransaction(options.cache, fileHash, uploadResult.id)\n return {\n cacheHit: false,\n transactionId: uploadResult.id,\n updatedCache,\n }\n }\n\n return {\n cacheHit: false,\n transactionId: uploadResult.id,\n }\n}\n\n/** Default concurrency for parallel file uploads */\nconst DEFAULT_UPLOAD_CONCURRENCY = 10\n\ninterface FileUploadTask {\n cached?: { transactionId: string }\n fullPath: string\n hash: string\n relativePath: string\n}\n\n/**\n * Upload a folder with per-file deduplication.\n * Each file is checked against the cache individually, and only uncached files are uploaded.\n * A manifest is then constructed and uploaded to create the folder structure.\n */\nexport async function uploadFolder(\n turbo: TurboAuthenticatedClient,\n folderPath: string,\n options?: {\n cache?: TransactionCache\n concurrency?: number\n fundingMode?: OnDemandFunding\n throwOnFailure?: boolean\n },\n): Promise<FolderUploadResult> {\n const concurrency = options?.concurrency ?? DEFAULT_UPLOAD_CONCURRENCY\n const useCache = options?.cache !== undefined\n\n // Get all files in the folder\n const relativePaths = getAllFiles(folderPath)\n\n if (relativePaths.length === 0) {\n throw new Error('Folder is empty, nothing to upload')\n }\n\n // Prepare file tasks with hashes (if caching is enabled)\n const tasks: FileUploadTask[] = await Promise.all(\n relativePaths.map(async (relativePath) => {\n const fullPath = path.join(folderPath, relativePath)\n const hash = useCache ? await hashFile(fullPath) : ''\n return { fullPath, hash, relativePath }\n }),\n )\n\n // Check cache for each file\n let cache = options?.cache ?? {}\n let cacheHits = 0\n\n for (const task of tasks) {\n if (useCache && task.hash) {\n const cached = getCachedTransaction(cache, task.hash)\n if (cached) {\n task.cached = { transactionId: cached.transactionId }\n cache = touchCacheEntry(cache, task.hash)\n cacheHits++\n }\n }\n }\n\n // If all files are cached, we still need to build and upload a new manifest\n // (because the manifest itself has a unique transaction ID each time)\n const uncachedTasks = tasks.filter((t) => !t.cached)\n\n // Upload uncached files with concurrency control using p-limit\n const limit = pLimit(concurrency)\n\n const uploadResults = await Promise.all(\n uncachedTasks.map((task) =>\n limit(async () => {\n const mimeType = mime.lookup(task.fullPath) || 'application/octet-stream'\n\n const uploadResult = await turbo.uploadFile({\n dataItemOpts: {\n tags: [\n { name: 'App-Name', value: 'Permaweb-Deploy' },\n { name: 'Content-Type', value: mimeType },\n ],\n },\n file: task.fullPath,\n ...(options?.fundingMode && { fundingMode: options.fundingMode }),\n })\n\n if (!uploadResult?.id) {\n if (options?.throwOnFailure) {\n throw new Error(`Failed to upload file: ${task.relativePath}`)\n }\n\n return { hash: task.hash, task, transactionId: null }\n }\n\n return { hash: task.hash, task, transactionId: uploadResult.id }\n }),\n ),\n )\n\n // Update cache with all successful uploads (done sequentially to avoid race conditions)\n for (const result of uploadResults) {\n if (useCache && result.hash && result.transactionId) {\n cache = setCachedTransaction(cache, result.hash, result.transactionId)\n }\n }\n\n // Check for any failed uploads\n const failedUploads = uploadResults.filter((r) => r.transactionId === null)\n if (failedUploads.length > 0 && options?.throwOnFailure) {\n throw new Error(\n `Failed to upload ${failedUploads.length} file(s): ${failedUploads.map((f) => f.task.relativePath).join(', ')}`,\n )\n }\n\n // Build manifest paths from cached and newly uploaded files\n const manifestPaths: Record<string, { id: string }> = {}\n\n for (const task of tasks) {\n let transactionId: string | null = null\n\n if (task.cached) {\n transactionId = task.cached.transactionId\n } else {\n const uploadResult = uploadResults.find((r) => r.task === task)\n transactionId = uploadResult?.transactionId ?? null\n }\n\n if (transactionId) {\n manifestPaths[task.relativePath] = { id: transactionId }\n\n // Add directory index support: if file is dir/index.html, also add dir → same ID\n if (task.relativePath.endsWith('/index.html')) {\n const dirPath = task.relativePath.replace(/\\/index\\.html$/, '')\n manifestPaths[dirPath] = { id: transactionId }\n }\n }\n }\n\n // Determine the index path (root index.html)\n const indexPath = relativePaths.includes('index.html') ? 'index.html' : undefined\n\n // Build the manifest\n const manifest = {\n manifest: 'arweave/paths',\n version: '0.2.0',\n ...(indexPath && { index: { path: indexPath } }),\n paths: manifestPaths,\n }\n\n // Upload the manifest\n const manifestBuffer = Buffer.from(JSON.stringify(manifest))\n const manifestUploadResult = await turbo.uploadFile({\n dataItemOpts: {\n tags: [\n { name: 'App-Name', value: 'Permaweb-Deploy' },\n { name: 'Content-Type', value: 'application/x.arweave-manifest+json' },\n ],\n },\n fileSizeFactory: () => manifestBuffer.length,\n fileStreamFactory: () => Readable.from(manifestBuffer),\n ...(options?.fundingMode && { fundingMode: options.fundingMode }),\n })\n\n if (!manifestUploadResult?.id) {\n throw new Error('Failed to upload manifest: upload result missing transaction ID')\n }\n\n return {\n cacheHit: cacheHits === tasks.length,\n cacheHits,\n totalFiles: tasks.length,\n transactionId: manifestUploadResult.id,\n updatedCache: useCache ? cache : undefined,\n uploaded: uncachedTasks.length - failedUploads.length,\n }\n}\n"],"names":[],"mappings":"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","x_google_ignoreList":[1,2]}
@@ -11,7 +11,7 @@ import ora from 'ora';
11
11
  import { deployFlagConfigs } from '../constants/flags.js';
12
12
  import { promptAdvancedOptions } from '../prompts/arns.js';
13
13
  import { getWalletConfig } from '../prompts/wallet.js';
14
- import { l as loadCache, u as uploadFile, c as cleanupCache, s as saveCache, a as uploadFolder } from '../chunks/uploader-DifbCz3u.js';
14
+ import { l as loadCache, u as uploadFile, c as cleanupCache, s as saveCache, a as uploadFolder } from '../chunks/uploader-d40K7e4Z.js';
15
15
  import { extractFlags, resolveConfig } from '../utils/config-resolver.js';
16
16
  import { expandPath } from '../utils/path.js';
17
17
  import { createSigner } from '../utils/signer.js';
@@ -65,13 +65,15 @@ class Deploy extends Command {
65
65
  const options = await promptAdvancedOptions();
66
66
  advancedOptions = options || void 0;
67
67
  }
68
+ const effectiveCacheMaxEntries = baseConfig["no-dedupe"] ? 0 : baseConfig["dedupe-cache-max-entries"];
68
69
  const deployConfig = {
69
70
  "ario-process": advancedOptions?.arioProcess || baseConfig["ario-process"],
70
71
  "arns-name": baseConfig["arns-name"],
71
- "cache-max-entries": baseConfig["cache-max-entries"],
72
+ "dedupe-cache-max-entries": effectiveCacheMaxEntries,
72
73
  "deploy-file": baseConfig["deploy-file"],
73
74
  "deploy-folder": baseConfig["deploy-folder"],
74
75
  "max-token-amount": advancedOptions?.maxTokenAmount || baseConfig["max-token-amount"],
76
+ "no-dedupe": baseConfig["no-dedupe"],
75
77
  "on-demand": advancedOptions?.onDemand || baseConfig["on-demand"],
76
78
  "private-key": walletConfig.privateKey,
77
79
  "sig-type": baseConfig["sig-type"],
@@ -194,28 +196,30 @@ class Deploy extends Command {
194
196
  if (deployConfig["deploy-file"]) {
195
197
  const filePath = expandPath(deployConfig["deploy-file"]);
196
198
  spinner.start(`Uploading file ${chalk.yellow(deployConfig["deploy-file"])}`);
197
- let cache = loadCache();
199
+ let cache = deployConfig["dedupe-cache-max-entries"] > 0 ? loadCache() : {};
198
200
  const uploadResult = await uploadFile(turbo, filePath, { cache, fundingMode });
199
201
  if (!uploadResult.transactionId) {
200
202
  spinner.fail("File upload failed: no transaction ID returned");
201
203
  this.error("File upload failed: no transaction ID returned");
202
204
  }
203
205
  txOrManifestId = uploadResult.transactionId;
204
- if (uploadResult.updatedCache) {
205
- cache = cleanupCache(uploadResult.updatedCache, deployConfig["cache-max-entries"]);
206
+ if (uploadResult.updatedCache && deployConfig["dedupe-cache-max-entries"] > 0) {
207
+ cache = cleanupCache(
208
+ uploadResult.updatedCache,
209
+ deployConfig["dedupe-cache-max-entries"]
210
+ );
206
211
  saveCache(cache);
207
212
  }
208
213
  if (uploadResult.cacheHit) {
209
214
  spinner.succeed(`File cache hit - reusing transaction ${chalk.green(txOrManifestId)}`);
210
215
  } else {
211
- spinner.succeed(
212
- `File uploaded: ${chalk.green(txOrManifestId)} ${chalk.gray("(cached for future deployments)")}`
213
- );
216
+ const cacheMsg = deployConfig["dedupe-cache-max-entries"] > 0 ? chalk.gray("(cached for future deployments)") : "";
217
+ spinner.succeed(`File uploaded: ${chalk.green(txOrManifestId)} ${cacheMsg}`.trim());
214
218
  }
215
219
  } else {
216
220
  const folderPath = expandPath(deployConfig["deploy-folder"]);
217
221
  spinner.start(`Uploading folder ${chalk.yellow(deployConfig["deploy-folder"])}`);
218
- let cache = loadCache();
222
+ let cache = deployConfig["dedupe-cache-max-entries"] > 0 ? loadCache() : {};
219
223
  const uploadResult = await uploadFolder(turbo, folderPath, {
220
224
  cache,
221
225
  fundingMode,
@@ -226,17 +230,23 @@ class Deploy extends Command {
226
230
  this.error("Folder upload failed: no transaction ID returned");
227
231
  }
228
232
  txOrManifestId = uploadResult.transactionId;
229
- if (uploadResult.updatedCache) {
230
- cache = cleanupCache(uploadResult.updatedCache, deployConfig["cache-max-entries"]);
233
+ if (uploadResult.updatedCache && deployConfig["dedupe-cache-max-entries"] > 0) {
234
+ cache = cleanupCache(
235
+ uploadResult.updatedCache,
236
+ deployConfig["dedupe-cache-max-entries"]
237
+ );
231
238
  saveCache(cache);
232
239
  }
240
+ const { cacheHits, totalFiles, uploaded } = uploadResult;
241
+ const statsMsg = cacheHits > 0 ? chalk.gray(` (${cacheHits}/${totalFiles} files cached, ${uploaded} uploaded)`) : "";
233
242
  if (uploadResult.cacheHit) {
234
243
  spinner.succeed(
235
- `Folder cache hit - reusing transaction ${chalk.green(txOrManifestId)}`
244
+ `All ${totalFiles} files cached - manifest: ${chalk.green(txOrManifestId)}`
236
245
  );
237
246
  } else {
247
+ const cacheMsg = deployConfig["dedupe-cache-max-entries"] > 0 ? chalk.gray(" (files cached for future deployments)") : "";
238
248
  spinner.succeed(
239
- `Folder uploaded: ${chalk.green(txOrManifestId)} ${chalk.gray("(cached for future deployments)")}`
249
+ `Folder uploaded: ${chalk.green(txOrManifestId)}${statsMsg}${cacheMsg}`
240
250
  );
241
251
  }
242
252
  }