@taqueria/plugin-ipfs-pinata 0.25.16-rc → 0.25.19-rc

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/index.cjs ADDED
@@ -0,0 +1,353 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
18
+ mod
19
+ ));
20
+
21
+ // index.ts
22
+ var import_node_sdk2 = require("@taqueria/node-sdk");
23
+
24
+ // src/proxy.ts
25
+ var import_node_sdk = require("@taqueria/node-sdk");
26
+ var import_path2 = __toESM(require("path"), 1);
27
+
28
+ // src/file-processing.ts
29
+ var import_promises = __toESM(require("fs/promises"), 1);
30
+ var import_path = __toESM(require("path"), 1);
31
+ async function* getFiles(fileOrDirPath) {
32
+ const dirInfo = await import_promises.default.stat(fileOrDirPath);
33
+ if (dirInfo.isFile()) {
34
+ yield fileOrDirPath;
35
+ return;
36
+ }
37
+ const dirents = await import_promises.default.readdir(fileOrDirPath, { withFileTypes: true });
38
+ for (const dirent of dirents) {
39
+ const res = import_path.default.resolve(fileOrDirPath, dirent.name);
40
+ if (dirent.isDirectory()) {
41
+ yield* getFiles(res);
42
+ } else {
43
+ yield res;
44
+ }
45
+ }
46
+ }
47
+ var createFileProvider = async ({
48
+ fileOrDirPath,
49
+ filter,
50
+ shouldEstimateFileCount
51
+ }) => {
52
+ fileOrDirPath = import_path.default.resolve(fileOrDirPath);
53
+ const pathInfo = await import_promises.default.stat(fileOrDirPath);
54
+ if (!pathInfo.isFile() && !pathInfo.isDirectory()) {
55
+ throw new Error(`The path '${fileOrDirPath}' is not a file or directory`);
56
+ }
57
+ let estimateFileCount = void 0;
58
+ if (shouldEstimateFileCount) {
59
+ estimateFileCount = 0;
60
+ for await (const filePath of getFiles(fileOrDirPath)) {
61
+ if (filter && !filter(filePath)) {
62
+ continue;
63
+ }
64
+ estimateFileCount++;
65
+ }
66
+ }
67
+ const fileGenerator = getFiles(fileOrDirPath);
68
+ const getNextFile = async () => {
69
+ let nextFile = (await fileGenerator.next()).value;
70
+ if (!filter) {
71
+ return nextFile;
72
+ }
73
+ while (nextFile && !filter(nextFile)) {
74
+ nextFile = await getNextFile();
75
+ }
76
+ return nextFile;
77
+ };
78
+ return {
79
+ getNextFile,
80
+ estimateFileCount
81
+ };
82
+ };
83
+ var processFiles = async ({
84
+ fileOrDirPath,
85
+ processFile,
86
+ filter,
87
+ parallelCount = 10,
88
+ onProgress
89
+ }) => {
90
+ const { getNextFile, estimateFileCount } = await createFileProvider({
91
+ fileOrDirPath,
92
+ filter,
93
+ shouldEstimateFileCount: true
94
+ });
95
+ const successes = [];
96
+ const failures = [];
97
+ onProgress == null ? void 0 : onProgress({
98
+ processedFilesCount: 0,
99
+ estimateFileCount
100
+ });
101
+ await Promise.all([...new Array(parallelCount)].map(async (x) => {
102
+ let fileToProcess = await getNextFile();
103
+ while (fileToProcess) {
104
+ const progressInfo = {
105
+ processedFilesCount: successes.length + failures.length,
106
+ estimateFileCount
107
+ };
108
+ onProgress == null ? void 0 : onProgress(progressInfo);
109
+ try {
110
+ const result = await processFile(fileToProcess, progressInfo);
111
+ successes.push({ filePath: fileToProcess, result });
112
+ } catch (err) {
113
+ failures.push({ filePath: fileToProcess, error: err });
114
+ }
115
+ fileToProcess = await getNextFile();
116
+ }
117
+ }));
118
+ onProgress == null ? void 0 : onProgress({
119
+ processedFilesCount: successes.length + failures.length,
120
+ estimateFileCount
121
+ });
122
+ return {
123
+ successes,
124
+ failures
125
+ };
126
+ };
127
+
128
+ // src/pinata-api.ts
129
+ var import_form_data = __toESM(require("form-data"), 1);
130
+ var import_fs = __toESM(require("fs"), 1);
131
+ var import_ipfs_only_hash = __toESM(require("ipfs-only-hash"), 1);
132
+ var import_node_fetch = __toESM(require("node-fetch"), 1);
133
+ var publishFileToIpfs = async ({
134
+ auth,
135
+ item
136
+ }) => {
137
+ const data = new import_form_data.default();
138
+ data.append("file", import_fs.default.createReadStream(item.filePath));
139
+ data.append(
140
+ "pinataMetadata",
141
+ JSON.stringify({
142
+ name: item.name
143
+ })
144
+ );
145
+ const response = await (0, import_node_fetch.default)(`https://api.pinata.cloud/pinning/pinFileToIPFS`, {
146
+ headers: {
147
+ Authorization: `Bearer ${auth.pinataJwtToken}`,
148
+ "Content-Type": `multipart/form-data; boundary=${data._boundary}`
149
+ },
150
+ body: data,
151
+ method: "post"
152
+ });
153
+ if (!response.ok) {
154
+ throw new Error(`Failed to upload '${item.name}' to ipfs ${response.statusText}`);
155
+ }
156
+ const uploadResult = await response.json();
157
+ return {
158
+ ipfsHash: uploadResult.IpfsHash
159
+ };
160
+ };
161
+ var pinHash = async ({
162
+ auth,
163
+ ipfsHash
164
+ }) => {
165
+ const response = await (0, import_node_fetch.default)(`https://api.pinata.cloud/pinning/pinByHash`, {
166
+ headers: {
167
+ Authorization: `Bearer ${auth.pinataJwtToken}`,
168
+ "Content-Type": "application/json"
169
+ },
170
+ method: "post",
171
+ body: JSON.stringify({
172
+ hashToPin: ipfsHash
173
+ })
174
+ });
175
+ if (!response.ok) {
176
+ throw new Error(`Failed to pin '${ipfsHash}' with pinata: ${response.statusText}`);
177
+ }
178
+ return;
179
+ };
180
+
181
+ // src/utils.ts
182
+ async function delay(timeout) {
183
+ return await new Promise((resolve) => {
184
+ setTimeout(resolve, timeout);
185
+ });
186
+ }
187
+ var createProcessBackoffController = ({
188
+ retryCount = 5,
189
+ targetRequestsPerMinute = 180
190
+ }) => {
191
+ let averageTimePerRequest = 5e3;
192
+ let targetTimePerRequest = 6e4 / targetRequestsPerMinute;
193
+ let lastTime = Date.now();
194
+ const processWithBackoff = async (process2) => {
195
+ let attempt = 0;
196
+ let lastError = void 0;
197
+ while (attempt < retryCount) {
198
+ try {
199
+ let delayTimeMs = Math.max(10, targetTimePerRequest - averageTimePerRequest);
200
+ await delay(Math.floor(delayTimeMs * (1 + 0.5 * Math.random())));
201
+ const result = await process2();
202
+ const timeNow = Date.now();
203
+ const timeElapsed = timeNow - lastTime;
204
+ lastTime = timeNow;
205
+ averageTimePerRequest = averageTimePerRequest * 0.97 + timeElapsed * 0.03;
206
+ return result;
207
+ } catch (err) {
208
+ lastError = err;
209
+ }
210
+ averageTimePerRequest -= (attempt + 1) * 1e3;
211
+ attempt++;
212
+ }
213
+ throw lastError;
214
+ };
215
+ return {
216
+ processWithBackoff
217
+ };
218
+ };
219
+
220
+ // src/proxy.ts
221
+ var import_config = require("dotenv/config");
222
+ var publishToIpfs = async (fileOrDirPath, auth) => {
223
+ if (!fileOrDirPath) {
224
+ throw new Error(`path was not provided`);
225
+ }
226
+ const { processWithBackoff } = createProcessBackoffController({
227
+ retryCount: 5,
228
+ targetRequestsPerMinute: 180
229
+ });
230
+ const result = await processFiles({
231
+ fileOrDirPath,
232
+ parallelCount: 10,
233
+ processFile: async (filePath) => {
234
+ return processWithBackoff(
235
+ () => publishFileToIpfs({
236
+ auth,
237
+ item: { filePath, name: import_path2.default.basename(filePath) }
238
+ })
239
+ );
240
+ },
241
+ onProgress: ({ processedFilesCount, estimateFileCount }) => {
242
+ if (estimateFileCount && processedFilesCount % 10) {
243
+ let ratio = processedFilesCount / estimateFileCount;
244
+ if (ratio > 1)
245
+ ratio = 1;
246
+ }
247
+ }
248
+ });
249
+ return {
250
+ render: "table",
251
+ data: [
252
+ ...result.failures.map((x) => {
253
+ var _a;
254
+ return {
255
+ "?": "\u274C",
256
+ filePath: x.filePath,
257
+ ipfsHash: void 0,
258
+ error: ((_a = x.error) == null ? void 0 : _a.message) ?? JSON.stringify(x.error)
259
+ };
260
+ }),
261
+ ...result.successes.map((x) => ({
262
+ "?": "\u2714",
263
+ filePath: x.filePath,
264
+ ipfsHash: x.result.ipfsHash,
265
+ error: void 0
266
+ }))
267
+ ]
268
+ };
269
+ };
270
+ var pinToIpfs = async (hash, auth) => {
271
+ if (!hash) {
272
+ throw new Error(`ipfs hash was not provided`);
273
+ }
274
+ await pinHash({ ipfsHash: hash, auth });
275
+ return {
276
+ render: "table",
277
+ data: [{ ipfsHash: hash }]
278
+ };
279
+ };
280
+ var execute = async (opts) => {
281
+ const {
282
+ task,
283
+ path: path3,
284
+ hash
285
+ } = opts;
286
+ const auth = {
287
+ pinataJwtToken: process.env["pinataJwtToken"]
288
+ };
289
+ if (!auth.pinataJwtToken) {
290
+ throw new Error(`The 'credentials.pinataJwtToken' was not found in config`);
291
+ }
292
+ switch (task) {
293
+ case "publish":
294
+ return publishToIpfs(path3, auth);
295
+ case "pin":
296
+ return pinToIpfs(hash, auth);
297
+ default:
298
+ throw new Error(`${task} is not an understood task by the ipfs-pinata plugin`);
299
+ }
300
+ };
301
+ var proxy_default = async (args) => {
302
+ const opts = args;
303
+ try {
304
+ const resultRaw = await execute(opts);
305
+ const result = "data" in resultRaw ? resultRaw.data : resultRaw;
306
+ return (0, import_node_sdk.sendJsonRes)(result);
307
+ } catch (err) {
308
+ const error = err;
309
+ if (error.message) {
310
+ return (0, import_node_sdk.sendAsyncErr)(error.message);
311
+ }
312
+ }
313
+ };
314
+
315
+ // index.ts
316
+ import_node_sdk2.Plugin.create(() => ({
317
+ schema: "0.1",
318
+ version: "0.4.0",
319
+ alias: "pinata",
320
+ tasks: [
321
+ import_node_sdk2.Task.create({
322
+ task: "publish",
323
+ command: "publish [path]",
324
+ description: "Upload and pin files using your pinata account.",
325
+ aliases: [],
326
+ handler: "proxy",
327
+ positionals: [
328
+ import_node_sdk2.PositionalArg.create({
329
+ placeholder: "path",
330
+ description: "Directory or file path to publish",
331
+ type: "string"
332
+ })
333
+ ],
334
+ encoding: "json"
335
+ }),
336
+ import_node_sdk2.Task.create({
337
+ task: "pin",
338
+ command: "pin [hash]",
339
+ description: "Pin a file already on ipfs with your pinata account.",
340
+ aliases: [],
341
+ handler: "proxy",
342
+ positionals: [
343
+ import_node_sdk2.PositionalArg.create({
344
+ placeholder: "hash",
345
+ description: "Ipfs hash of the file or directory that is already on the ipfs network.",
346
+ type: "string"
347
+ })
348
+ ]
349
+ })
350
+ ],
351
+ proxy: proxy_default
352
+ }), process.argv);
353
+ //# sourceMappingURL=index.cjs.map
package/index.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["index.ts","src/proxy.ts","src/file-processing.ts","src/pinata-api.ts","src/utils.ts"],"sourcesContent":["import { Option, Plugin, PositionalArg, Task } from '@taqueria/node-sdk';\nimport proxy from './src/proxy';\n\nPlugin.create(() => ({\n\tschema: '0.1',\n\tversion: '0.4.0',\n\talias: 'pinata',\n\ttasks: [\n\t\tTask.create({\n\t\t\ttask: 'publish',\n\t\t\tcommand: 'publish [path]',\n\t\t\tdescription: 'Upload and pin files using your pinata account.',\n\t\t\taliases: [],\n\t\t\thandler: 'proxy',\n\t\t\tpositionals: [\n\t\t\t\tPositionalArg.create({\n\t\t\t\t\tplaceholder: 'path',\n\t\t\t\t\tdescription: 'Directory or file path to publish',\n\t\t\t\t\ttype: 'string',\n\t\t\t\t}),\n\t\t\t],\n\t\t\tencoding: 'json',\n\t\t}),\n\t\tTask.create({\n\t\t\ttask: 'pin',\n\t\t\tcommand: 'pin [hash]',\n\t\t\tdescription: 'Pin a file already on ipfs with your pinata account.',\n\t\t\taliases: [],\n\t\t\thandler: 'proxy',\n\t\t\tpositionals: [\n\t\t\t\tPositionalArg.create({\n\t\t\t\t\tplaceholder: 'hash',\n\t\t\t\t\tdescription: 'Ipfs hash of the file or directory that is already on the ipfs network.',\n\t\t\t\t\ttype: 'string',\n\t\t\t\t}),\n\t\t\t],\n\t\t}),\n\t],\n\tproxy,\n}), process.argv);\n","import { sendAsyncErr, sendAsyncRes, sendErr, sendJsonRes } from '@taqueria/node-sdk';\nimport { RequestArgs } from '@taqueria/node-sdk';\nimport { LoadedConfig, SanitizedAbsPath } from '@taqueria/node-sdk/types';\nimport path from 'path';\nimport { processFiles } from './file-processing';\nimport { PinataAuth, pinHash, publishFileToIpfs } from './pinata-api';\nimport { createProcessBackoffController } from './utils';\n\n// Load .env for jwt token\n// TODO: How should this be stored in a secure way?\nimport 'dotenv/config';\n\n// TODO: What should this be, it was removed from the sdk\ntype PluginResponse =\n\t| void\n\t| {\n\t\trender: 'table';\n\t\tdata: unknown[];\n\t};\n\ninterface Opts extends RequestArgs.t {\n\treadonly path?: string;\n\treadonly hash?: string;\n\treadonly task?: string;\n}\n\nconst publishToIpfs = async (fileOrDirPath: undefined | string, auth: PinataAuth): Promise<PluginResponse> => {\n\tif (!fileOrDirPath) {\n\t\tthrow new Error(`path was not provided`);\n\t}\n\n\t// Pinata is limited to 180 requests per minute\n\t// So for the first 180 requests they can go fast\n\n\tconst { processWithBackoff } = createProcessBackoffController({\n\t\tretryCount: 5,\n\t\ttargetRequestsPerMinute: 180,\n\t});\n\n\tconst result = await processFiles({\n\t\tfileOrDirPath,\n\t\tparallelCount: 10,\n\t\tprocessFile: async filePath => {\n\t\t\t// // TEMP: Debug\n\t\t\t// console.log(`publishing: ${filePath}`);\n\n\t\t\treturn processWithBackoff(() =>\n\t\t\t\tpublishFileToIpfs({\n\t\t\t\t\tauth,\n\t\t\t\t\titem: { filePath, name: path.basename(filePath) },\n\t\t\t\t})\n\t\t\t);\n\t\t},\n\t\tonProgress: ({ processedFilesCount, estimateFileCount }) => {\n\t\t\tif (estimateFileCount && processedFilesCount % 10) {\n\t\t\t\tlet ratio = processedFilesCount / estimateFileCount;\n\t\t\t\tif (ratio > 1) ratio = 1;\n\n\t\t\t\t// // TODO: Call task sdk progress\n\t\t\t\t// console.log(`Progress: ${(ratio * 100).toFixed(0)}%`);\n\t\t\t}\n\t\t},\n\t});\n\n\t// // TEMP: DEBUG: Show error\n\t// if (result.failures.length) {\n\t// \tconsole.log('❗ Failures:\\n' + result.failures.map(f => `${f.filePath}: ${f.error}`).join('\\n'));\n\t// }\n\n\treturn {\n\t\trender: 'table',\n\t\tdata: [\n\t\t\t...result.failures.map(x => ({\n\t\t\t\t'?': '❌',\n\t\t\t\tfilePath: x.filePath,\n\t\t\t\tipfsHash: undefined,\n\t\t\t\terror: (x.error as { message?: string })?.message ?? JSON.stringify(x.error),\n\t\t\t})),\n\t\t\t...result.successes.map(x => ({\n\t\t\t\t'?': '✔',\n\t\t\t\tfilePath: x.filePath,\n\t\t\t\tipfsHash: x.result.ipfsHash,\n\t\t\t\terror: undefined,\n\t\t\t})),\n\t\t],\n\t};\n};\n\nconst pinToIpfs = async (hash: undefined | string, auth: PinataAuth): Promise<PluginResponse> => {\n\tif (!hash) {\n\t\tthrow new Error(`ipfs hash was not provided`);\n\t}\n\n\tawait pinHash({ ipfsHash: hash, auth });\n\n\treturn {\n\t\trender: 'table',\n\t\tdata: [{ ipfsHash: hash }],\n\t};\n};\n\nconst execute = async (opts: Opts): Promise<PluginResponse> => {\n\tconst {\n\t\ttask,\n\t\tpath,\n\t\thash,\n\t} = opts;\n\n\tconst auth: PinataAuth = {\n\t\t// TODO: Where should this be stored?\n\t\t// pinataJwtToken: (config as Record<string, any>).credentials.pinataJwtToken,\n\t\tpinataJwtToken: process.env['pinataJwtToken'] as string,\n\t};\n\n\tif (!auth.pinataJwtToken) {\n\t\tthrow new Error(`The 'credentials.pinataJwtToken' was not found in config`);\n\t}\n\n\tswitch (task) {\n\t\tcase 'publish':\n\t\t\treturn publishToIpfs(path, auth);\n\t\tcase 'pin':\n\t\t\treturn pinToIpfs(hash, auth);\n\t\tdefault:\n\t\t\tthrow new Error(`${task} is not an understood task by the ipfs-pinata plugin`);\n\t}\n};\n\nexport default async (args: RequestArgs.t): Promise<PluginResponse> => {\n\tconst opts = args as Opts;\n\n\ttry {\n\t\tconst resultRaw = await execute(opts) as Record<string, unknown>;\n\t\t// TODO: Fix deno parsing\n\t\t// Without this, `data.reduce is not a function`\n\t\tconst result = ('data' in resultRaw) ? resultRaw.data : resultRaw;\n\t\treturn sendJsonRes(result);\n\t} catch (err) {\n\t\tconst error = err as Error;\n\t\tif (error.message) {\n\t\t\treturn sendAsyncErr(error.message);\n\t\t}\n\t}\n};\n","import fs from 'fs/promises';\nimport path from 'path';\n\n// Async generator\n// https://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search\nasync function* getFiles(fileOrDirPath: string): AsyncGenerator<string, void, unknown> {\n\tconst dirInfo = await fs.stat(fileOrDirPath);\n\tif (dirInfo.isFile()) {\n\t\tyield fileOrDirPath;\n\t\treturn;\n\t}\n\n\tconst dirents = await fs.readdir(fileOrDirPath, { withFileTypes: true });\n\tfor (const dirent of dirents) {\n\t\tconst res = path.resolve(fileOrDirPath, dirent.name);\n\t\tif (dirent.isDirectory()) {\n\t\t\tyield* getFiles(res);\n\t\t} else {\n\t\t\tyield res;\n\t\t}\n\t}\n}\n\nconst createFileProvider = async ({\n\tfileOrDirPath,\n\tfilter,\n\tshouldEstimateFileCount,\n}: {\n\tfileOrDirPath: string;\n\tfilter?: (filePath: string) => boolean;\n\tshouldEstimateFileCount?: boolean;\n}) => {\n\tfileOrDirPath = path.resolve(fileOrDirPath);\n\tconst pathInfo = await fs.stat(fileOrDirPath);\n\tif (\n\t\t!pathInfo.isFile()\n\t\t&& !pathInfo.isDirectory()\n\t) {\n\t\tthrow new Error(`The path '${fileOrDirPath}' is not a file or directory`);\n\t}\n\n\tlet estimateFileCount = undefined as undefined | number;\n\tif (shouldEstimateFileCount) {\n\t\testimateFileCount = 0;\n\t\tfor await (const filePath of getFiles(fileOrDirPath)) {\n\t\t\tif (filter && !filter(filePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\testimateFileCount++;\n\t\t}\n\t}\n\n\tconst fileGenerator = getFiles(fileOrDirPath);\n\tconst getNextFile = async () => {\n\t\tlet nextFile = (await fileGenerator.next()).value;\n\t\tif (!filter) {\n\t\t\treturn nextFile;\n\t\t}\n\n\t\twhile (nextFile && !filter(nextFile)) {\n\t\t\tnextFile = await getNextFile();\n\t\t}\n\n\t\treturn nextFile;\n\t};\n\treturn {\n\t\tgetNextFile,\n\t\testimateFileCount,\n\t};\n};\n\ntype ProgressInfo = { processedFilesCount: number; estimateFileCount: undefined | number };\nexport const processFiles = async <TResult>({\n\tfileOrDirPath,\n\tprocessFile,\n\tfilter,\n\tparallelCount = 10,\n\tonProgress,\n}: {\n\tfileOrDirPath: string;\n\tprocessFile: (filePath: string, progress: ProgressInfo) => Promise<TResult>;\n\tfilter?: (filePath: string) => boolean;\n\tparallelCount?: number;\n\tonProgress?: (progress: ProgressInfo) => void;\n}) => {\n\tconst { getNextFile, estimateFileCount } = await createFileProvider({\n\t\tfileOrDirPath,\n\t\tfilter,\n\t\tshouldEstimateFileCount: true,\n\t});\n\n\tconst successes = [] as { filePath: string; result: TResult }[];\n\tconst failures = [] as { filePath: string; error: unknown }[];\n\n\tonProgress?.({\n\t\tprocessedFilesCount: 0,\n\t\testimateFileCount,\n\t});\n\n\tawait Promise.all([...new Array(parallelCount)].map(async x => {\n\t\tlet fileToProcess = await getNextFile();\n\t\twhile (fileToProcess) {\n\t\t\tconst progressInfo = {\n\t\t\t\tprocessedFilesCount: successes.length + failures.length,\n\t\t\t\testimateFileCount,\n\t\t\t};\n\t\t\tonProgress?.(progressInfo);\n\n\t\t\ttry {\n\t\t\t\tconst result = await processFile(fileToProcess, progressInfo);\n\t\t\t\tsuccesses.push({ filePath: fileToProcess, result });\n\t\t\t} catch (err) {\n\t\t\t\tfailures.push({ filePath: fileToProcess, error: err });\n\t\t\t}\n\n\t\t\tfileToProcess = await getNextFile();\n\t\t}\n\t}));\n\n\tonProgress?.({\n\t\tprocessedFilesCount: successes.length + failures.length,\n\t\testimateFileCount,\n\t});\n\n\treturn {\n\t\tsuccesses,\n\t\tfailures,\n\t};\n};\n","import FormData from 'form-data';\nimport fs from 'fs';\nimport Hash from 'ipfs-only-hash';\nimport fetch from 'node-fetch';\n\nexport type PinataAuth = {\n\tpinataJwtToken: string;\n};\n\nexport type PublishFileResult = {\n\tipfsHash: string;\n};\n\nexport const publishFileToIpfs = async ({\n\tauth,\n\titem,\n}: {\n\tauth: PinataAuth;\n\titem: {\n\t\tname: string;\n\t\tfilePath: string;\n\t};\n}): Promise<PublishFileResult> => {\n\t// The data api to check for existing file is limited to 30 requests per minute\n\t// While uploading allows 180 requests per minute\n\t// i.e. it's faster to just upload again\n\n\t// // Skip if already pinned\n\t// const { isPinned, ipfsHash } = await checkIfFileIsPinned({ auth, item });\n\t// if (isPinned) {\n\t// \treturn {\n\t// \t\tipfsHash,\n\t// \t};\n\t// }\n\n\tconst data = new FormData();\n\tdata.append('file', fs.createReadStream(item.filePath));\n\tdata.append(\n\t\t'pinataMetadata',\n\t\tJSON.stringify({\n\t\t\tname: item.name,\n\t\t}),\n\t);\n\n\tconst response = await fetch(`https://api.pinata.cloud/pinning/pinFileToIPFS`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t\t'Content-Type': `multipart/form-data; boundary=${(data as unknown as { _boundary: string })._boundary}`,\n\t\t},\n\t\tbody: data,\n\t\tmethod: 'post',\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to upload '${item.name}' to ipfs ${response.statusText}`);\n\t}\n\n\tconst uploadResult = await response.json() as {\n\t\tIpfsHash: string; // This is the IPFS multi-hash provided back for your content,\n\t\tPinSize: string; // This is how large (in bytes) the content you just pinned is,\n\t\tTimestamp: string; // This is the timestamp for your content pinning (represented in ISO 8601 format)\n\t};\n\n\treturn {\n\t\tipfsHash: uploadResult.IpfsHash,\n\t};\n};\n\nconst checkIfFileIsPinned = async ({\n\tauth,\n\titem,\n}: {\n\tauth: PinataAuth;\n\titem: {\n\t\tname: string;\n\t\tfilePath: string;\n\t};\n}) => {\n\tconst ipfsHash = await Hash.of(fs.createReadStream(item.filePath));\n\n\tconst response = await fetch(`https://api.pinata.cloud/data/pinList?status=pinned&hashContains=${ipfsHash}`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t},\n\t\tmethod: 'get',\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to query '${item.name}' status from pinata ${response.statusText}`);\n\t}\n\n\tconst pinResult = await response.json() as {\n\t\tcount: number;\n\t\trows: {\n\t\t\tid: string;\n\t\t\tipfs_pin_hash: string;\n\t\t\tsize: number;\n\t\t\tuser_id: string;\n\t\t\tdate_pinned: null | string;\n\t\t\tdate_unpinned: null | string;\n\t\t\tmetadata: {\n\t\t\t\tname: string;\n\t\t\t\tkeyvalues: null | string;\n\t\t\t};\n\t\t\tregions: {\n\t\t\t\tregionId: string;\n\t\t\t\tcurrentReplicationCount: number;\n\t\t\t\tdesiredReplicationCount: number;\n\t\t\t}[];\n\t\t}[];\n\t};\n\n\tconst isPinned = pinResult.rows.some(x =>\n\t\tx.ipfs_pin_hash === ipfsHash\n\t\t&& x.date_pinned\n\t\t&& !x.date_unpinned\n\t);\n\n\treturn {\n\t\tisPinned,\n\t\tipfsHash,\n\t};\n};\n\nexport const pinHash = async ({\n\tauth,\n\tipfsHash,\n}: {\n\tauth: PinataAuth;\n\tipfsHash: string;\n}) => {\n\tconst response = await fetch(`https://api.pinata.cloud/pinning/pinByHash`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t\t'Content-Type': 'application/json',\n\t\t},\n\t\tmethod: 'post',\n\t\tbody: JSON.stringify({\n\t\t\thashToPin: ipfsHash,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to pin '${ipfsHash}' with pinata: ${response.statusText}`);\n\t}\n\n\t// Ok is the only response if successful\n\treturn;\n};\n","export async function delay(timeout: number): Promise<void> {\n\treturn await new Promise(resolve => {\n\t\tsetTimeout(resolve, timeout);\n\t});\n}\n\nexport const createProcessBackoffController = ({\n\tretryCount = 5,\n\ttargetRequestsPerMinute = 180,\n}: {\n\tretryCount?: number;\n\ttargetRequestsPerMinute?: number;\n}) => {\n\tlet averageTimePerRequest = 5000;\n\tlet targetTimePerRequest = 60000 / targetRequestsPerMinute;\n\tlet lastTime = Date.now();\n\n\tconst processWithBackoff = async <TResult>(process: () => Promise<TResult>) => {\n\t\tlet attempt = 0;\n\t\tlet lastError = undefined as unknown;\n\t\twhile (attempt < retryCount) {\n\t\t\ttry {\n\t\t\t\tlet delayTimeMs = Math.max(10, targetTimePerRequest - averageTimePerRequest);\n\n\t\t\t\t// Partially randomized delay to ensure parallel requests don't line up\n\t\t\t\tawait delay(Math.floor(delayTimeMs * (1 + 0.5 * Math.random())));\n\n\t\t\t\tconst result = await process();\n\n\t\t\t\tconst timeNow = Date.now();\n\t\t\t\tconst timeElapsed = timeNow - lastTime;\n\t\t\t\tlastTime = timeNow;\n\n\t\t\t\t// Running average\n\t\t\t\taverageTimePerRequest = averageTimePerRequest * 0.97 + timeElapsed * 0.03;\n\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tlastError = err;\n\t\t\t}\n\n\t\t\t// Quickly increase time to wait if failure (allow negatives to wait longer than target)\n\t\t\taverageTimePerRequest -= (attempt + 1) * 1000;\n\t\t\tattempt++;\n\t\t}\n\n\t\t// All attempts failed\n\t\tthrow lastError;\n\t};\n\n\treturn {\n\t\tprocessWithBackoff,\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,mBAAoD;;;ACApD,sBAAiE;AAGjE,IAAAC,eAAiB;;;ACHjB,sBAAe;AACf,kBAAiB;AAIjB,gBAAgB,SAAS,eAA8D;AACtF,QAAM,UAAU,MAAM,gBAAAC,QAAG,KAAK,aAAa;AAC3C,MAAI,QAAQ,OAAO,GAAG;AACrB,UAAM;AACN;AAAA,EACD;AAEA,QAAM,UAAU,MAAM,gBAAAA,QAAG,QAAQ,eAAe,EAAE,eAAe,KAAK,CAAC;AACvE,aAAW,UAAU,SAAS;AAC7B,UAAM,MAAM,YAAAC,QAAK,QAAQ,eAAe,OAAO,IAAI;AACnD,QAAI,OAAO,YAAY,GAAG;AACzB,aAAO,SAAS,GAAG;AAAA,IACpB,OAAO;AACN,YAAM;AAAA,IACP;AAAA,EACD;AACD;AAEA,IAAM,qBAAqB,OAAO;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACD,MAIM;AACL,kBAAgB,YAAAA,QAAK,QAAQ,aAAa;AAC1C,QAAM,WAAW,MAAM,gBAAAD,QAAG,KAAK,aAAa;AAC5C,MACC,CAAC,SAAS,OAAO,KACd,CAAC,SAAS,YAAY,GACxB;AACD,UAAM,IAAI,MAAM,aAAa,2CAA2C;AAAA,EACzE;AAEA,MAAI,oBAAoB;AACxB,MAAI,yBAAyB;AAC5B,wBAAoB;AACpB,qBAAiB,YAAY,SAAS,aAAa,GAAG;AACrD,UAAI,UAAU,CAAC,OAAO,QAAQ,GAAG;AAChC;AAAA,MACD;AACA;AAAA,IACD;AAAA,EACD;AAEA,QAAM,gBAAgB,SAAS,aAAa;AAC5C,QAAM,cAAc,YAAY;AAC/B,QAAI,YAAY,MAAM,cAAc,KAAK,GAAG;AAC5C,QAAI,CAAC,QAAQ;AACZ,aAAO;AAAA,IACR;AAEA,WAAO,YAAY,CAAC,OAAO,QAAQ,GAAG;AACrC,iBAAW,MAAM,YAAY;AAAA,IAC9B;AAEA,WAAO;AAAA,EACR;AACA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,EACD;AACD;AAGO,IAAM,eAAe,OAAgB;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AACD,MAMM;AACL,QAAM,EAAE,aAAa,kBAAkB,IAAI,MAAM,mBAAmB;AAAA,IACnE;AAAA,IACA;AAAA,IACA,yBAAyB;AAAA,EAC1B,CAAC;AAED,QAAM,YAAY,CAAC;AACnB,QAAM,WAAW,CAAC;AAElB,2CAAa;AAAA,IACZ,qBAAqB;AAAA,IACrB;AAAA,EACD;AAEA,QAAM,QAAQ,IAAI,CAAC,GAAG,IAAI,MAAM,aAAa,CAAC,EAAE,IAAI,OAAM,MAAK;AAC9D,QAAI,gBAAgB,MAAM,YAAY;AACtC,WAAO,eAAe;AACrB,YAAM,eAAe;AAAA,QACpB,qBAAqB,UAAU,SAAS,SAAS;AAAA,QACjD;AAAA,MACD;AACA,+CAAa;AAEb,UAAI;AACH,cAAM,SAAS,MAAM,YAAY,eAAe,YAAY;AAC5D,kBAAU,KAAK,EAAE,UAAU,eAAe,OAAO,CAAC;AAAA,MACnD,SAAS,KAAP;AACD,iBAAS,KAAK,EAAE,UAAU,eAAe,OAAO,IAAI,CAAC;AAAA,MACtD;AAEA,sBAAgB,MAAM,YAAY;AAAA,IACnC;AAAA,EACD,CAAC,CAAC;AAEF,2CAAa;AAAA,IACZ,qBAAqB,UAAU,SAAS,SAAS;AAAA,IACjD;AAAA,EACD;AAEA,SAAO;AAAA,IACN;AAAA,IACA;AAAA,EACD;AACD;;;AChIA,uBAAqB;AACrB,gBAAe;AACf,4BAAiB;AACjB,wBAAkB;AAUX,IAAM,oBAAoB,OAAO;AAAA,EACvC;AAAA,EACA;AACD,MAMkC;AAajC,QAAM,OAAO,IAAI,iBAAAE,QAAS;AAC1B,OAAK,OAAO,QAAQ,UAAAC,QAAG,iBAAiB,KAAK,QAAQ,CAAC;AACtD,OAAK;AAAA,IACJ;AAAA,IACA,KAAK,UAAU;AAAA,MACd,MAAM,KAAK;AAAA,IACZ,CAAC;AAAA,EACF;AAEA,QAAM,WAAW,UAAM,kBAAAC,SAAM,kDAAkD;AAAA,IAC9E,SAAS;AAAA,MACR,eAAe,UAAU,KAAK;AAAA,MAC9B,gBAAgB,iCAAkC,KAA0C;AAAA,IAC7F;AAAA,IACA,MAAM;AAAA,IACN,QAAQ;AAAA,EACT,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,IAAI,MAAM,qBAAqB,KAAK,iBAAiB,SAAS,YAAY;AAAA,EACjF;AAEA,QAAM,eAAe,MAAM,SAAS,KAAK;AAMzC,SAAO;AAAA,IACN,UAAU,aAAa;AAAA,EACxB;AACD;AA0DO,IAAM,UAAU,OAAO;AAAA,EAC7B;AAAA,EACA;AACD,MAGM;AACL,QAAM,WAAW,UAAM,kBAAAC,SAAM,8CAA8C;AAAA,IAC1E,SAAS;AAAA,MACR,eAAe,UAAU,KAAK;AAAA,MAC9B,gBAAgB;AAAA,IACjB;AAAA,IACA,QAAQ;AAAA,IACR,MAAM,KAAK,UAAU;AAAA,MACpB,WAAW;AAAA,IACZ,CAAC;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,IAAI,MAAM,kBAAkB,0BAA0B,SAAS,YAAY;AAAA,EAClF;AAGA;AACD;;;ACpJA,eAAsB,MAAM,SAAgC;AAC3D,SAAO,MAAM,IAAI,QAAQ,aAAW;AACnC,eAAW,SAAS,OAAO;AAAA,EAC5B,CAAC;AACF;AAEO,IAAM,iCAAiC,CAAC;AAAA,EAC9C,aAAa;AAAA,EACb,0BAA0B;AAC3B,MAGM;AACL,MAAI,wBAAwB;AAC5B,MAAI,uBAAuB,MAAQ;AACnC,MAAI,WAAW,KAAK,IAAI;AAExB,QAAM,qBAAqB,OAAgBC,aAAoC;AAC9E,QAAI,UAAU;AACd,QAAI,YAAY;AAChB,WAAO,UAAU,YAAY;AAC5B,UAAI;AACH,YAAI,cAAc,KAAK,IAAI,IAAI,uBAAuB,qBAAqB;AAG3E,cAAM,MAAM,KAAK,MAAM,eAAe,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;AAE/D,cAAM,SAAS,MAAMA,SAAQ;AAE7B,cAAM,UAAU,KAAK,IAAI;AACzB,cAAM,cAAc,UAAU;AAC9B,mBAAW;AAGX,gCAAwB,wBAAwB,OAAO,cAAc;AAErE,eAAO;AAAA,MACR,SAAS,KAAP;AACD,oBAAY;AAAA,MACb;AAGA,gCAA0B,UAAU,KAAK;AACzC;AAAA,IACD;AAGA,UAAM;AAAA,EACP;AAEA,SAAO;AAAA,IACN;AAAA,EACD;AACD;;;AH3CA,oBAAO;AAgBP,IAAM,gBAAgB,OAAO,eAAmC,SAA8C;AAC7G,MAAI,CAAC,eAAe;AACnB,UAAM,IAAI,MAAM,uBAAuB;AAAA,EACxC;AAKA,QAAM,EAAE,mBAAmB,IAAI,+BAA+B;AAAA,IAC7D,YAAY;AAAA,IACZ,yBAAyB;AAAA,EAC1B,CAAC;AAED,QAAM,SAAS,MAAM,aAAa;AAAA,IACjC;AAAA,IACA,eAAe;AAAA,IACf,aAAa,OAAM,aAAY;AAI9B,aAAO;AAAA,QAAmB,MACzB,kBAAkB;AAAA,UACjB;AAAA,UACA,MAAM,EAAE,UAAU,MAAM,aAAAC,QAAK,SAAS,QAAQ,EAAE;AAAA,QACjD,CAAC;AAAA,MACF;AAAA,IACD;AAAA,IACA,YAAY,CAAC,EAAE,qBAAqB,kBAAkB,MAAM;AAC3D,UAAI,qBAAqB,sBAAsB,IAAI;AAClD,YAAI,QAAQ,sBAAsB;AAClC,YAAI,QAAQ;AAAG,kBAAQ;AAAA,MAIxB;AAAA,IACD;AAAA,EACD,CAAC;AAOD,SAAO;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,GAAG,OAAO,SAAS,IAAI,OAAE;AAxE5B;AAwEgC;AAAA,UAC5B,KAAK;AAAA,UACL,UAAU,EAAE;AAAA,UACZ,UAAU;AAAA,UACV,SAAQ,OAAE,UAAF,mBAAkC,YAAW,KAAK,UAAU,EAAE,KAAK;AAAA,QAC5E;AAAA,OAAE;AAAA,MACF,GAAG,OAAO,UAAU,IAAI,QAAM;AAAA,QAC7B,KAAK;AAAA,QACL,UAAU,EAAE;AAAA,QACZ,UAAU,EAAE,OAAO;AAAA,QACnB,OAAO;AAAA,MACR,EAAE;AAAA,IACH;AAAA,EACD;AACD;AAEA,IAAM,YAAY,OAAO,MAA0B,SAA8C;AAChG,MAAI,CAAC,MAAM;AACV,UAAM,IAAI,MAAM,4BAA4B;AAAA,EAC7C;AAEA,QAAM,QAAQ,EAAE,UAAU,MAAM,KAAK,CAAC;AAEtC,SAAO;AAAA,IACN,QAAQ;AAAA,IACR,MAAM,CAAC,EAAE,UAAU,KAAK,CAAC;AAAA,EAC1B;AACD;AAEA,IAAM,UAAU,OAAO,SAAwC;AAC9D,QAAM;AAAA,IACL;AAAA,IACA,MAAAA;AAAA,IACA;AAAA,EACD,IAAI;AAEJ,QAAM,OAAmB;AAAA,IAGxB,gBAAgB,QAAQ,IAAI;AAAA,EAC7B;AAEA,MAAI,CAAC,KAAK,gBAAgB;AACzB,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC3E;AAEA,UAAQ,MAAM;AAAA,IACb,KAAK;AACJ,aAAO,cAAcA,OAAM,IAAI;AAAA,IAChC,KAAK;AACJ,aAAO,UAAU,MAAM,IAAI;AAAA,IAC5B;AACC,YAAM,IAAI,MAAM,GAAG,0DAA0D;AAAA,EAC/E;AACD;AAEA,IAAO,gBAAQ,OAAO,SAAiD;AACtE,QAAM,OAAO;AAEb,MAAI;AACH,UAAM,YAAY,MAAM,QAAQ,IAAI;AAGpC,UAAM,SAAU,UAAU,YAAa,UAAU,OAAO;AACxD,eAAO,6BAAY,MAAM;AAAA,EAC1B,SAAS,KAAP;AACD,UAAM,QAAQ;AACd,QAAI,MAAM,SAAS;AAClB,iBAAO,8BAAa,MAAM,OAAO;AAAA,IAClC;AAAA,EACD;AACD;;;AD5IA,wBAAO,OAAO,OAAO;AAAA,EACpB,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AAAA,EACP,OAAO;AAAA,IACN,sBAAK,OAAO;AAAA,MACX,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,SAAS,CAAC;AAAA,MACV,SAAS;AAAA,MACT,aAAa;AAAA,QACZ,+BAAc,OAAO;AAAA,UACpB,aAAa;AAAA,UACb,aAAa;AAAA,UACb,MAAM;AAAA,QACP,CAAC;AAAA,MACF;AAAA,MACA,UAAU;AAAA,IACX,CAAC;AAAA,IACD,sBAAK,OAAO;AAAA,MACX,MAAM;AAAA,MACN,SAAS;AAAA,MACT,aAAa;AAAA,MACb,SAAS,CAAC;AAAA,MACV,SAAS;AAAA,MACT,aAAa;AAAA,QACZ,+BAAc,OAAO;AAAA,UACpB,aAAa;AAAA,UACb,aAAa;AAAA,UACb,MAAM;AAAA,QACP,CAAC;AAAA,MACF;AAAA,IACD,CAAC;AAAA,EACF;AAAA,EACA;AACD,IAAI,QAAQ,IAAI;","names":["import_node_sdk","import_path","fs","path","FormData","fs","fetch","fetch","process","path"]}
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+
package/index.js CHANGED
@@ -1,351 +1,333 @@
1
- import {Plugin as $khbt3$Plugin, Task as $khbt3$Task, PositionalArg as $khbt3$PositionalArg, sendJsonRes as $khbt3$sendJsonRes, sendAsyncErr as $khbt3$sendAsyncErr} from "@taqueria/node-sdk";
2
- import $khbt3$path from "path";
3
- import "dotenv/config";
4
- import $khbt3$fspromises from "fs/promises";
5
- import $khbt3$formdata from "form-data";
6
- import $khbt3$fs from "fs";
7
- import $khbt3$ipfsonlyhash from "ipfs-only-hash";
8
- import $khbt3$nodefetch from "node-fetch";
9
-
10
-
11
-
1
+ // index.ts
2
+ import { Plugin, PositionalArg, Task } from "@taqueria/node-sdk";
12
3
 
4
+ // src/proxy.ts
5
+ import { sendAsyncErr, sendJsonRes } from "@taqueria/node-sdk";
6
+ import path2 from "path";
13
7
 
14
-
15
- // Async generator
16
- // https://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
17
- async function* $11f5d2da33680230$var$getFiles(fileOrDirPath) {
18
- const dirInfo = await (0, $khbt3$fspromises).stat(fileOrDirPath);
19
- if (dirInfo.isFile()) {
20
- yield fileOrDirPath;
21
- return;
22
- }
23
- const dirents = await (0, $khbt3$fspromises).readdir(fileOrDirPath, {
24
- withFileTypes: true
25
- });
26
- for (const dirent of dirents){
27
- const res = (0, $khbt3$path).resolve(fileOrDirPath, dirent.name);
28
- if (dirent.isDirectory()) yield* $11f5d2da33680230$var$getFiles(res);
29
- else yield res;
8
+ // src/file-processing.ts
9
+ import fs from "fs/promises";
10
+ import path from "path";
11
+ async function* getFiles(fileOrDirPath) {
12
+ const dirInfo = await fs.stat(fileOrDirPath);
13
+ if (dirInfo.isFile()) {
14
+ yield fileOrDirPath;
15
+ return;
16
+ }
17
+ const dirents = await fs.readdir(fileOrDirPath, { withFileTypes: true });
18
+ for (const dirent of dirents) {
19
+ const res = path.resolve(fileOrDirPath, dirent.name);
20
+ if (dirent.isDirectory()) {
21
+ yield* getFiles(res);
22
+ } else {
23
+ yield res;
30
24
  }
25
+ }
31
26
  }
32
- const $11f5d2da33680230$var$createFileProvider = async ({ fileOrDirPath: fileOrDirPath , filter: filter , shouldEstimateFileCount: shouldEstimateFileCount })=>{
33
- fileOrDirPath = (0, $khbt3$path).resolve(fileOrDirPath);
34
- const pathInfo = await (0, $khbt3$fspromises).stat(fileOrDirPath);
35
- if (!pathInfo.isFile() && !pathInfo.isDirectory()) throw new Error(`The path '${fileOrDirPath}' is not a file or directory`);
36
- let estimateFileCount = undefined;
37
- if (shouldEstimateFileCount) {
38
- estimateFileCount = 0;
39
- for await (const filePath of $11f5d2da33680230$var$getFiles(fileOrDirPath)){
40
- if (filter && !filter(filePath)) continue;
41
- estimateFileCount++;
42
- }
27
+ var createFileProvider = async ({
28
+ fileOrDirPath,
29
+ filter,
30
+ shouldEstimateFileCount
31
+ }) => {
32
+ fileOrDirPath = path.resolve(fileOrDirPath);
33
+ const pathInfo = await fs.stat(fileOrDirPath);
34
+ if (!pathInfo.isFile() && !pathInfo.isDirectory()) {
35
+ throw new Error(`The path '${fileOrDirPath}' is not a file or directory`);
36
+ }
37
+ let estimateFileCount = void 0;
38
+ if (shouldEstimateFileCount) {
39
+ estimateFileCount = 0;
40
+ for await (const filePath of getFiles(fileOrDirPath)) {
41
+ if (filter && !filter(filePath)) {
42
+ continue;
43
+ }
44
+ estimateFileCount++;
45
+ }
46
+ }
47
+ const fileGenerator = getFiles(fileOrDirPath);
48
+ const getNextFile = async () => {
49
+ let nextFile = (await fileGenerator.next()).value;
50
+ if (!filter) {
51
+ return nextFile;
43
52
  }
44
- const fileGenerator = $11f5d2da33680230$var$getFiles(fileOrDirPath);
45
- const getNextFile = async ()=>{
46
- let nextFile = (await fileGenerator.next()).value;
47
- if (!filter) return nextFile;
48
- while(nextFile && !filter(nextFile))nextFile = await getNextFile();
49
- return nextFile;
50
- };
51
- return {
52
- getNextFile: getNextFile,
53
- estimateFileCount: estimateFileCount
54
- };
53
+ while (nextFile && !filter(nextFile)) {
54
+ nextFile = await getNextFile();
55
+ }
56
+ return nextFile;
57
+ };
58
+ return {
59
+ getNextFile,
60
+ estimateFileCount
61
+ };
55
62
  };
56
- const $11f5d2da33680230$export$8f4b23801f8f7529 = async ({ fileOrDirPath: fileOrDirPath , processFile: processFile , filter: filter , parallelCount: parallelCount = 10 , onProgress: onProgress })=>{
57
- const { getNextFile: getNextFile , estimateFileCount: estimateFileCount } = await $11f5d2da33680230$var$createFileProvider({
58
- fileOrDirPath: fileOrDirPath,
59
- filter: filter,
60
- shouldEstimateFileCount: true
61
- });
62
- const successes = [];
63
- const failures = [];
64
- onProgress?.({
65
- processedFilesCount: 0,
66
- estimateFileCount: estimateFileCount
67
- });
68
- await Promise.all([
69
- ...new Array(parallelCount)
70
- ].map(async (x)=>{
71
- let fileToProcess = await getNextFile();
72
- while(fileToProcess){
73
- const progressInfo = {
74
- processedFilesCount: successes.length + failures.length,
75
- estimateFileCount: estimateFileCount
76
- };
77
- onProgress?.(progressInfo);
78
- try {
79
- const result = await processFile(fileToProcess, progressInfo);
80
- successes.push({
81
- filePath: fileToProcess,
82
- result: result
83
- });
84
- } catch (err) {
85
- failures.push({
86
- filePath: fileToProcess,
87
- error: err
88
- });
89
- }
90
- fileToProcess = await getNextFile();
91
- }
92
- }));
93
- onProgress?.({
63
+ var processFiles = async ({
64
+ fileOrDirPath,
65
+ processFile,
66
+ filter,
67
+ parallelCount = 10,
68
+ onProgress
69
+ }) => {
70
+ const { getNextFile, estimateFileCount } = await createFileProvider({
71
+ fileOrDirPath,
72
+ filter,
73
+ shouldEstimateFileCount: true
74
+ });
75
+ const successes = [];
76
+ const failures = [];
77
+ onProgress == null ? void 0 : onProgress({
78
+ processedFilesCount: 0,
79
+ estimateFileCount
80
+ });
81
+ await Promise.all([...new Array(parallelCount)].map(async (x) => {
82
+ let fileToProcess = await getNextFile();
83
+ while (fileToProcess) {
84
+ const progressInfo = {
94
85
  processedFilesCount: successes.length + failures.length,
95
- estimateFileCount: estimateFileCount
96
- });
97
- return {
98
- successes: successes,
99
- failures: failures
100
- };
86
+ estimateFileCount
87
+ };
88
+ onProgress == null ? void 0 : onProgress(progressInfo);
89
+ try {
90
+ const result = await processFile(fileToProcess, progressInfo);
91
+ successes.push({ filePath: fileToProcess, result });
92
+ } catch (err) {
93
+ failures.push({ filePath: fileToProcess, error: err });
94
+ }
95
+ fileToProcess = await getNextFile();
96
+ }
97
+ }));
98
+ onProgress == null ? void 0 : onProgress({
99
+ processedFilesCount: successes.length + failures.length,
100
+ estimateFileCount
101
+ });
102
+ return {
103
+ successes,
104
+ failures
105
+ };
101
106
  };
102
107
 
103
-
104
-
105
-
106
-
107
-
108
- const $3f348f69899f8bee$export$77ce72def6804f3 = async ({ auth: auth , item: item })=>{
109
- // The data api to check for existing file is limited to 30 requests per minute
110
- // While uploading allows 180 requests per minute
111
- // i.e. it's faster to just upload again
112
- // // Skip if already pinned
113
- // const { isPinned, ipfsHash } = await checkIfFileIsPinned({ auth, item });
114
- // if (isPinned) {
115
- // return {
116
- // ipfsHash,
117
- // };
118
- // }
119
- const data = new (0, $khbt3$formdata)();
120
- data.append("file", (0, $khbt3$fs).createReadStream(item.filePath));
121
- data.append("pinataMetadata", JSON.stringify({
122
- name: item.name
123
- }));
124
- const response = await (0, $khbt3$nodefetch)(`https://api.pinata.cloud/pinning/pinFileToIPFS`, {
125
- headers: {
126
- Authorization: `Bearer ${auth.pinataJwtToken}`,
127
- "Content-Type": `multipart/form-data; boundary=${data._boundary}`
128
- },
129
- body: data,
130
- method: "post"
131
- });
132
- if (!response.ok) throw new Error(`Failed to upload '${item.name}' to ipfs ${response.statusText}`);
133
- const uploadResult = await response.json();
134
- return {
135
- ipfsHash: uploadResult.IpfsHash
136
- };
108
+ // src/pinata-api.ts
109
+ import FormData from "form-data";
110
+ import fs2 from "fs";
111
+ import Hash from "ipfs-only-hash";
112
+ import fetch from "node-fetch";
113
+ var publishFileToIpfs = async ({
114
+ auth,
115
+ item
116
+ }) => {
117
+ const data = new FormData();
118
+ data.append("file", fs2.createReadStream(item.filePath));
119
+ data.append(
120
+ "pinataMetadata",
121
+ JSON.stringify({
122
+ name: item.name
123
+ })
124
+ );
125
+ const response = await fetch(`https://api.pinata.cloud/pinning/pinFileToIPFS`, {
126
+ headers: {
127
+ Authorization: `Bearer ${auth.pinataJwtToken}`,
128
+ "Content-Type": `multipart/form-data; boundary=${data._boundary}`
129
+ },
130
+ body: data,
131
+ method: "post"
132
+ });
133
+ if (!response.ok) {
134
+ throw new Error(`Failed to upload '${item.name}' to ipfs ${response.statusText}`);
135
+ }
136
+ const uploadResult = await response.json();
137
+ return {
138
+ ipfsHash: uploadResult.IpfsHash
139
+ };
137
140
  };
138
- const $3f348f69899f8bee$var$checkIfFileIsPinned = async ({ auth: auth , item: item })=>{
139
- const ipfsHash = await (0, $khbt3$ipfsonlyhash).of((0, $khbt3$fs).createReadStream(item.filePath));
140
- const response = await (0, $khbt3$nodefetch)(`https://api.pinata.cloud/data/pinList?status=pinned&hashContains=${ipfsHash}`, {
141
- headers: {
142
- Authorization: `Bearer ${auth.pinataJwtToken}`
143
- },
144
- method: "get"
145
- });
146
- if (!response.ok) throw new Error(`Failed to query '${item.name}' status from pinata ${response.statusText}`);
147
- const pinResult = await response.json();
148
- const isPinned = pinResult.rows.some((x)=>x.ipfs_pin_hash === ipfsHash && x.date_pinned && !x.date_unpinned);
149
- return {
150
- isPinned: isPinned,
151
- ipfsHash: ipfsHash
152
- };
153
- };
154
- const $3f348f69899f8bee$export$979daf0e3ae9695f = async ({ auth: auth , ipfsHash: ipfsHash })=>{
155
- const response = await (0, $khbt3$nodefetch)(`https://api.pinata.cloud/pinning/pinByHash`, {
156
- headers: {
157
- Authorization: `Bearer ${auth.pinataJwtToken}`,
158
- "Content-Type": "application/json"
159
- },
160
- method: "post",
161
- body: JSON.stringify({
162
- hashToPin: ipfsHash
163
- })
164
- });
165
- if (!response.ok) throw new Error(`Failed to pin '${ipfsHash}' with pinata: ${response.statusText}`);
166
- // Ok is the only response if successful
167
- return;
141
+ var pinHash = async ({
142
+ auth,
143
+ ipfsHash
144
+ }) => {
145
+ const response = await fetch(`https://api.pinata.cloud/pinning/pinByHash`, {
146
+ headers: {
147
+ Authorization: `Bearer ${auth.pinataJwtToken}`,
148
+ "Content-Type": "application/json"
149
+ },
150
+ method: "post",
151
+ body: JSON.stringify({
152
+ hashToPin: ipfsHash
153
+ })
154
+ });
155
+ if (!response.ok) {
156
+ throw new Error(`Failed to pin '${ipfsHash}' with pinata: ${response.statusText}`);
157
+ }
158
+ return;
168
159
  };
169
160
 
170
-
171
- async function $81b0536a41de4891$export$1391212d75b2ee65(timeout) {
172
- return await new Promise((resolve)=>{
173
- setTimeout(resolve, timeout);
174
- });
161
+ // src/utils.ts
162
+ async function delay(timeout) {
163
+ return await new Promise((resolve) => {
164
+ setTimeout(resolve, timeout);
165
+ });
175
166
  }
176
- const $81b0536a41de4891$export$568be7ef485b9273 = ({ retryCount: retryCount = 5 , targetRequestsPerMinute: targetRequestsPerMinute = 180 })=>{
177
- let averageTimePerRequest = 5000;
178
- let targetTimePerRequest = 60000 / targetRequestsPerMinute;
179
- let lastTime = Date.now();
180
- const processWithBackoff = async (process)=>{
181
- let attempt = 0;
182
- let lastError = undefined;
183
- while(attempt < retryCount){
184
- try {
185
- let delayTimeMs = Math.max(10, targetTimePerRequest - averageTimePerRequest);
186
- // Partially randomized delay to ensure parallel requests don't line up
187
- await $81b0536a41de4891$export$1391212d75b2ee65(Math.floor(delayTimeMs * (1 + 0.5 * Math.random())));
188
- const result = await process();
189
- const timeNow = Date.now();
190
- const timeElapsed = timeNow - lastTime;
191
- lastTime = timeNow;
192
- // Running average
193
- averageTimePerRequest = averageTimePerRequest * 0.97 + timeElapsed * 0.03;
194
- return result;
195
- } catch (err) {
196
- lastError = err;
197
- }
198
- // Quickly increase time to wait if failure (allow negatives to wait longer than target)
199
- averageTimePerRequest -= (attempt + 1) * 1000;
200
- attempt++;
201
- }
202
- // All attempts failed
203
- throw lastError;
204
- };
205
- return {
206
- processWithBackoff: processWithBackoff
207
- };
167
+ var createProcessBackoffController = ({
168
+ retryCount = 5,
169
+ targetRequestsPerMinute = 180
170
+ }) => {
171
+ let averageTimePerRequest = 5e3;
172
+ let targetTimePerRequest = 6e4 / targetRequestsPerMinute;
173
+ let lastTime = Date.now();
174
+ const processWithBackoff = async (process2) => {
175
+ let attempt = 0;
176
+ let lastError = void 0;
177
+ while (attempt < retryCount) {
178
+ try {
179
+ let delayTimeMs = Math.max(10, targetTimePerRequest - averageTimePerRequest);
180
+ await delay(Math.floor(delayTimeMs * (1 + 0.5 * Math.random())));
181
+ const result = await process2();
182
+ const timeNow = Date.now();
183
+ const timeElapsed = timeNow - lastTime;
184
+ lastTime = timeNow;
185
+ averageTimePerRequest = averageTimePerRequest * 0.97 + timeElapsed * 0.03;
186
+ return result;
187
+ } catch (err) {
188
+ lastError = err;
189
+ }
190
+ averageTimePerRequest -= (attempt + 1) * 1e3;
191
+ attempt++;
192
+ }
193
+ throw lastError;
194
+ };
195
+ return {
196
+ processWithBackoff
197
+ };
208
198
  };
209
199
 
210
-
211
-
212
- const $e26bf59b4820ce36$var$publishToIpfs = async (fileOrDirPath, auth)=>{
213
- if (!fileOrDirPath) throw new Error(`path was not provided`);
214
- // Pinata is limited to 180 requests per minute
215
- // So for the first 180 requests they can go fast
216
- const { processWithBackoff: processWithBackoff } = (0, $81b0536a41de4891$export$568be7ef485b9273)({
217
- retryCount: 5,
218
- targetRequestsPerMinute: 180
219
- });
220
- const result = await (0, $11f5d2da33680230$export$8f4b23801f8f7529)({
221
- fileOrDirPath: fileOrDirPath,
222
- parallelCount: 10,
223
- processFile: async (filePath)=>{
224
- // // TEMP: Debug
225
- // console.log(`publishing: ${filePath}`);
226
- return processWithBackoff(()=>(0, $3f348f69899f8bee$export$77ce72def6804f3)({
227
- auth: auth,
228
- item: {
229
- filePath: filePath,
230
- name: (0, $khbt3$path).basename(filePath)
231
- }
232
- }));
233
- },
234
- onProgress: ({ processedFilesCount: processedFilesCount , estimateFileCount: estimateFileCount })=>{
235
- if (estimateFileCount && processedFilesCount % 10) {
236
- let ratio = processedFilesCount / estimateFileCount;
237
- if (ratio > 1) ratio = 1;
238
- // // TODO: Call task sdk progress
239
- // console.log(`Progress: ${(ratio * 100).toFixed(0)}%`);
240
- }
241
- }
242
- });
243
- // // TEMP: DEBUG: Show error
244
- // if (result.failures.length) {
245
- // console.log('❗ Failures:\n' + result.failures.map(f => `${f.filePath}: ${f.error}`).join('\n'));
246
- // }
247
- return {
248
- render: "table",
249
- data: [
250
- ...result.failures.map((x)=>({
251
- "?": "❌",
252
- filePath: x.filePath,
253
- ipfsHash: undefined,
254
- error: x.error?.message ?? JSON.stringify(x.error)
255
- })),
256
- ...result.successes.map((x)=>({
257
- "?": "✔",
258
- filePath: x.filePath,
259
- ipfsHash: x.result.ipfsHash,
260
- error: undefined
261
- }))
262
- ]
263
- };
200
+ // src/proxy.ts
201
+ import "dotenv/config";
202
+ var publishToIpfs = async (fileOrDirPath, auth) => {
203
+ if (!fileOrDirPath) {
204
+ throw new Error(`path was not provided`);
205
+ }
206
+ const { processWithBackoff } = createProcessBackoffController({
207
+ retryCount: 5,
208
+ targetRequestsPerMinute: 180
209
+ });
210
+ const result = await processFiles({
211
+ fileOrDirPath,
212
+ parallelCount: 10,
213
+ processFile: async (filePath) => {
214
+ return processWithBackoff(
215
+ () => publishFileToIpfs({
216
+ auth,
217
+ item: { filePath, name: path2.basename(filePath) }
218
+ })
219
+ );
220
+ },
221
+ onProgress: ({ processedFilesCount, estimateFileCount }) => {
222
+ if (estimateFileCount && processedFilesCount % 10) {
223
+ let ratio = processedFilesCount / estimateFileCount;
224
+ if (ratio > 1)
225
+ ratio = 1;
226
+ }
227
+ }
228
+ });
229
+ return {
230
+ render: "table",
231
+ data: [
232
+ ...result.failures.map((x) => {
233
+ var _a;
234
+ return {
235
+ "?": "\u274C",
236
+ filePath: x.filePath,
237
+ ipfsHash: void 0,
238
+ error: ((_a = x.error) == null ? void 0 : _a.message) ?? JSON.stringify(x.error)
239
+ };
240
+ }),
241
+ ...result.successes.map((x) => ({
242
+ "?": "\u2714",
243
+ filePath: x.filePath,
244
+ ipfsHash: x.result.ipfsHash,
245
+ error: void 0
246
+ }))
247
+ ]
248
+ };
264
249
  };
265
- const $e26bf59b4820ce36$var$pinToIpfs = async (hash, auth)=>{
266
- if (!hash) throw new Error(`ipfs hash was not provided`);
267
- await (0, $3f348f69899f8bee$export$979daf0e3ae9695f)({
268
- ipfsHash: hash,
269
- auth: auth
270
- });
271
- return {
272
- render: "table",
273
- data: [
274
- {
275
- ipfsHash: hash
276
- }
277
- ]
278
- };
250
+ var pinToIpfs = async (hash, auth) => {
251
+ if (!hash) {
252
+ throw new Error(`ipfs hash was not provided`);
253
+ }
254
+ await pinHash({ ipfsHash: hash, auth });
255
+ return {
256
+ render: "table",
257
+ data: [{ ipfsHash: hash }]
258
+ };
279
259
  };
280
- const $e26bf59b4820ce36$var$execute = async (opts)=>{
281
- const { task: task , path: path , hash: hash } = opts;
282
- const auth = {
283
- // TODO: Where should this be stored?
284
- // pinataJwtToken: (config as Record<string, any>).credentials.pinataJwtToken,
285
- pinataJwtToken: process.env["pinataJwtToken"]
286
- };
287
- if (!auth.pinataJwtToken) throw new Error(`The 'credentials.pinataJwtToken' was not found in config`);
288
- switch(task){
289
- case "publish":
290
- return $e26bf59b4820ce36$var$publishToIpfs(path, auth);
291
- case "pin":
292
- return $e26bf59b4820ce36$var$pinToIpfs(hash, auth);
293
- default:
294
- throw new Error(`${task} is not an understood task by the ipfs-pinata plugin`);
295
- }
260
+ var execute = async (opts) => {
261
+ const {
262
+ task,
263
+ path: path3,
264
+ hash
265
+ } = opts;
266
+ const auth = {
267
+ pinataJwtToken: process.env["pinataJwtToken"]
268
+ };
269
+ if (!auth.pinataJwtToken) {
270
+ throw new Error(`The 'credentials.pinataJwtToken' was not found in config`);
271
+ }
272
+ switch (task) {
273
+ case "publish":
274
+ return publishToIpfs(path3, auth);
275
+ case "pin":
276
+ return pinToIpfs(hash, auth);
277
+ default:
278
+ throw new Error(`${task} is not an understood task by the ipfs-pinata plugin`);
279
+ }
296
280
  };
297
- var $e26bf59b4820ce36$export$2e2bcd8739ae039 = async (args)=>{
298
- const opts = args;
299
- try {
300
- const resultRaw = await $e26bf59b4820ce36$var$execute(opts);
301
- // TODO: Fix deno parsing
302
- // Without this, `data.reduce is not a function`
303
- const result = "data" in resultRaw ? resultRaw.data : resultRaw;
304
- return (0, $khbt3$sendJsonRes)(result);
305
- } catch (err) {
306
- const error = err;
307
- if (error.message) return (0, $khbt3$sendAsyncErr)(error.message);
281
+ var proxy_default = async (args) => {
282
+ const opts = args;
283
+ try {
284
+ const resultRaw = await execute(opts);
285
+ const result = "data" in resultRaw ? resultRaw.data : resultRaw;
286
+ return sendJsonRes(result);
287
+ } catch (err) {
288
+ const error = err;
289
+ if (error.message) {
290
+ return sendAsyncErr(error.message);
308
291
  }
292
+ }
309
293
  };
310
294
 
311
-
312
- (0, $khbt3$Plugin).create(()=>({
313
- schema: "0.1",
314
- version: "0.4.0",
315
- alias: "pinata",
316
- tasks: [
317
- (0, $khbt3$Task).create({
318
- task: "publish",
319
- command: "publish [path]",
320
- description: "Upload and pin files using your pinata account.",
321
- aliases: [],
322
- handler: "proxy",
323
- positionals: [
324
- (0, $khbt3$PositionalArg).create({
325
- placeholder: "path",
326
- description: "Directory or file path to publish",
327
- type: "string"
328
- })
329
- ],
330
- encoding: "json"
331
- }),
332
- (0, $khbt3$Task).create({
333
- task: "pin",
334
- command: "pin [hash]",
335
- description: "Pin a file already on ipfs with your pinata account.",
336
- aliases: [],
337
- handler: "proxy",
338
- positionals: [
339
- (0, $khbt3$PositionalArg).create({
340
- placeholder: "hash",
341
- description: "Ipfs hash of the file or directory that is already on the ipfs network.",
342
- type: "string"
343
- })
344
- ]
345
- })
346
- ],
347
- proxy: $e26bf59b4820ce36$export$2e2bcd8739ae039
348
- }), process.argv);
349
-
350
-
351
- //# sourceMappingURL=index.js.map
295
+ // index.ts
296
+ Plugin.create(() => ({
297
+ schema: "0.1",
298
+ version: "0.4.0",
299
+ alias: "pinata",
300
+ tasks: [
301
+ Task.create({
302
+ task: "publish",
303
+ command: "publish [path]",
304
+ description: "Upload and pin files using your pinata account.",
305
+ aliases: [],
306
+ handler: "proxy",
307
+ positionals: [
308
+ PositionalArg.create({
309
+ placeholder: "path",
310
+ description: "Directory or file path to publish",
311
+ type: "string"
312
+ })
313
+ ],
314
+ encoding: "json"
315
+ }),
316
+ Task.create({
317
+ task: "pin",
318
+ command: "pin [hash]",
319
+ description: "Pin a file already on ipfs with your pinata account.",
320
+ aliases: [],
321
+ handler: "proxy",
322
+ positionals: [
323
+ PositionalArg.create({
324
+ placeholder: "hash",
325
+ description: "Ipfs hash of the file or directory that is already on the ipfs network.",
326
+ type: "string"
327
+ })
328
+ ]
329
+ })
330
+ ],
331
+ proxy: proxy_default
332
+ }), process.argv);
333
+ //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"mappings":"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","sources":["taqueria-plugin-ipfs-pinata/index.ts","taqueria-plugin-ipfs-pinata/src/proxy.ts","taqueria-plugin-ipfs-pinata/src/file-processing.ts","taqueria-plugin-ipfs-pinata/src/pinata-api.ts","taqueria-plugin-ipfs-pinata/src/utils.ts"],"sourcesContent":["import { Option, Plugin, PositionalArg, Task } from '@taqueria/node-sdk';\nimport proxy from './src/proxy';\n\nPlugin.create(() => ({\n\tschema: '0.1',\n\tversion: '0.4.0',\n\talias: 'pinata',\n\ttasks: [\n\t\tTask.create({\n\t\t\ttask: 'publish',\n\t\t\tcommand: 'publish [path]',\n\t\t\tdescription: 'Upload and pin files using your pinata account.',\n\t\t\taliases: [],\n\t\t\thandler: 'proxy',\n\t\t\tpositionals: [\n\t\t\t\tPositionalArg.create({\n\t\t\t\t\tplaceholder: 'path',\n\t\t\t\t\tdescription: 'Directory or file path to publish',\n\t\t\t\t\ttype: 'string',\n\t\t\t\t}),\n\t\t\t],\n\t\t\tencoding: 'json',\n\t\t}),\n\t\tTask.create({\n\t\t\ttask: 'pin',\n\t\t\tcommand: 'pin [hash]',\n\t\t\tdescription: 'Pin a file already on ipfs with your pinata account.',\n\t\t\taliases: [],\n\t\t\thandler: 'proxy',\n\t\t\tpositionals: [\n\t\t\t\tPositionalArg.create({\n\t\t\t\t\tplaceholder: 'hash',\n\t\t\t\t\tdescription: 'Ipfs hash of the file or directory that is already on the ipfs network.',\n\t\t\t\t\ttype: 'string',\n\t\t\t\t}),\n\t\t\t],\n\t\t}),\n\t],\n\tproxy,\n}), process.argv);\n","import { sendAsyncErr, sendAsyncRes, sendErr, sendJsonRes } from '@taqueria/node-sdk';\nimport { RequestArgs } from '@taqueria/node-sdk';\nimport { LoadedConfig, SanitizedAbsPath } from '@taqueria/node-sdk/types';\nimport path from 'path';\nimport { processFiles } from './file-processing';\nimport { PinataAuth, pinHash, publishFileToIpfs } from './pinata-api';\nimport { createProcessBackoffController } from './utils';\n\n// Load .env for jwt token\n// TODO: How should this be stored in a secure way?\nimport 'dotenv/config';\n\n// TODO: What should this be, it was removed from the sdk\ntype PluginResponse =\n\t| void\n\t| {\n\t\trender: 'table';\n\t\tdata: unknown[];\n\t};\n\ninterface Opts extends RequestArgs.t {\n\treadonly path?: string;\n\treadonly hash?: string;\n\treadonly task?: string;\n}\n\nconst publishToIpfs = async (fileOrDirPath: undefined | string, auth: PinataAuth): Promise<PluginResponse> => {\n\tif (!fileOrDirPath) {\n\t\tthrow new Error(`path was not provided`);\n\t}\n\n\t// Pinata is limited to 180 requests per minute\n\t// So for the first 180 requests they can go fast\n\n\tconst { processWithBackoff } = createProcessBackoffController({\n\t\tretryCount: 5,\n\t\ttargetRequestsPerMinute: 180,\n\t});\n\n\tconst result = await processFiles({\n\t\tfileOrDirPath,\n\t\tparallelCount: 10,\n\t\tprocessFile: async filePath => {\n\t\t\t// // TEMP: Debug\n\t\t\t// console.log(`publishing: ${filePath}`);\n\n\t\t\treturn processWithBackoff(() =>\n\t\t\t\tpublishFileToIpfs({\n\t\t\t\t\tauth,\n\t\t\t\t\titem: { filePath, name: path.basename(filePath) },\n\t\t\t\t})\n\t\t\t);\n\t\t},\n\t\tonProgress: ({ processedFilesCount, estimateFileCount }) => {\n\t\t\tif (estimateFileCount && processedFilesCount % 10) {\n\t\t\t\tlet ratio = processedFilesCount / estimateFileCount;\n\t\t\t\tif (ratio > 1) ratio = 1;\n\n\t\t\t\t// // TODO: Call task sdk progress\n\t\t\t\t// console.log(`Progress: ${(ratio * 100).toFixed(0)}%`);\n\t\t\t}\n\t\t},\n\t});\n\n\t// // TEMP: DEBUG: Show error\n\t// if (result.failures.length) {\n\t// \tconsole.log('❗ Failures:\\n' + result.failures.map(f => `${f.filePath}: ${f.error}`).join('\\n'));\n\t// }\n\n\treturn {\n\t\trender: 'table',\n\t\tdata: [\n\t\t\t...result.failures.map(x => ({\n\t\t\t\t'?': '❌',\n\t\t\t\tfilePath: x.filePath,\n\t\t\t\tipfsHash: undefined,\n\t\t\t\terror: (x.error as { message?: string })?.message ?? JSON.stringify(x.error),\n\t\t\t})),\n\t\t\t...result.successes.map(x => ({\n\t\t\t\t'?': '✔',\n\t\t\t\tfilePath: x.filePath,\n\t\t\t\tipfsHash: x.result.ipfsHash,\n\t\t\t\terror: undefined,\n\t\t\t})),\n\t\t],\n\t};\n};\n\nconst pinToIpfs = async (hash: undefined | string, auth: PinataAuth): Promise<PluginResponse> => {\n\tif (!hash) {\n\t\tthrow new Error(`ipfs hash was not provided`);\n\t}\n\n\tawait pinHash({ ipfsHash: hash, auth });\n\n\treturn {\n\t\trender: 'table',\n\t\tdata: [{ ipfsHash: hash }],\n\t};\n};\n\nconst execute = async (opts: Opts): Promise<PluginResponse> => {\n\tconst {\n\t\ttask,\n\t\tpath,\n\t\thash,\n\t} = opts;\n\n\tconst auth: PinataAuth = {\n\t\t// TODO: Where should this be stored?\n\t\t// pinataJwtToken: (config as Record<string, any>).credentials.pinataJwtToken,\n\t\tpinataJwtToken: process.env['pinataJwtToken'] as string,\n\t};\n\n\tif (!auth.pinataJwtToken) {\n\t\tthrow new Error(`The 'credentials.pinataJwtToken' was not found in config`);\n\t}\n\n\tswitch (task) {\n\t\tcase 'publish':\n\t\t\treturn publishToIpfs(path, auth);\n\t\tcase 'pin':\n\t\t\treturn pinToIpfs(hash, auth);\n\t\tdefault:\n\t\t\tthrow new Error(`${task} is not an understood task by the ipfs-pinata plugin`);\n\t}\n};\n\nexport default async (args: RequestArgs.t): Promise<PluginResponse> => {\n\tconst opts = args as Opts;\n\n\ttry {\n\t\tconst resultRaw = await execute(opts) as Record<string, unknown>;\n\t\t// TODO: Fix deno parsing\n\t\t// Without this, `data.reduce is not a function`\n\t\tconst result = ('data' in resultRaw) ? resultRaw.data : resultRaw;\n\t\treturn sendJsonRes(result);\n\t} catch (err) {\n\t\tconst error = err as Error;\n\t\tif (error.message) {\n\t\t\treturn sendAsyncErr(error.message);\n\t\t}\n\t}\n};\n","import fs from 'fs/promises';\nimport path from 'path';\n\n// Async generator\n// https://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search\nasync function* getFiles(fileOrDirPath: string): AsyncGenerator<string, void, unknown> {\n\tconst dirInfo = await fs.stat(fileOrDirPath);\n\tif (dirInfo.isFile()) {\n\t\tyield fileOrDirPath;\n\t\treturn;\n\t}\n\n\tconst dirents = await fs.readdir(fileOrDirPath, { withFileTypes: true });\n\tfor (const dirent of dirents) {\n\t\tconst res = path.resolve(fileOrDirPath, dirent.name);\n\t\tif (dirent.isDirectory()) {\n\t\t\tyield* getFiles(res);\n\t\t} else {\n\t\t\tyield res;\n\t\t}\n\t}\n}\n\nconst createFileProvider = async ({\n\tfileOrDirPath,\n\tfilter,\n\tshouldEstimateFileCount,\n}: {\n\tfileOrDirPath: string;\n\tfilter?: (filePath: string) => boolean;\n\tshouldEstimateFileCount?: boolean;\n}) => {\n\tfileOrDirPath = path.resolve(fileOrDirPath);\n\tconst pathInfo = await fs.stat(fileOrDirPath);\n\tif (\n\t\t!pathInfo.isFile()\n\t\t&& !pathInfo.isDirectory()\n\t) {\n\t\tthrow new Error(`The path '${fileOrDirPath}' is not a file or directory`);\n\t}\n\n\tlet estimateFileCount = undefined as undefined | number;\n\tif (shouldEstimateFileCount) {\n\t\testimateFileCount = 0;\n\t\tfor await (const filePath of getFiles(fileOrDirPath)) {\n\t\t\tif (filter && !filter(filePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\testimateFileCount++;\n\t\t}\n\t}\n\n\tconst fileGenerator = getFiles(fileOrDirPath);\n\tconst getNextFile = async () => {\n\t\tlet nextFile = (await fileGenerator.next()).value;\n\t\tif (!filter) {\n\t\t\treturn nextFile;\n\t\t}\n\n\t\twhile (nextFile && !filter(nextFile)) {\n\t\t\tnextFile = await getNextFile();\n\t\t}\n\n\t\treturn nextFile;\n\t};\n\treturn {\n\t\tgetNextFile,\n\t\testimateFileCount,\n\t};\n};\n\ntype ProgressInfo = { processedFilesCount: number; estimateFileCount: undefined | number };\nexport const processFiles = async <TResult>({\n\tfileOrDirPath,\n\tprocessFile,\n\tfilter,\n\tparallelCount = 10,\n\tonProgress,\n}: {\n\tfileOrDirPath: string;\n\tprocessFile: (filePath: string, progress: ProgressInfo) => Promise<TResult>;\n\tfilter?: (filePath: string) => boolean;\n\tparallelCount?: number;\n\tonProgress?: (progress: ProgressInfo) => void;\n}) => {\n\tconst { getNextFile, estimateFileCount } = await createFileProvider({\n\t\tfileOrDirPath,\n\t\tfilter,\n\t\tshouldEstimateFileCount: true,\n\t});\n\n\tconst successes = [] as { filePath: string; result: TResult }[];\n\tconst failures = [] as { filePath: string; error: unknown }[];\n\n\tonProgress?.({\n\t\tprocessedFilesCount: 0,\n\t\testimateFileCount,\n\t});\n\n\tawait Promise.all([...new Array(parallelCount)].map(async x => {\n\t\tlet fileToProcess = await getNextFile();\n\t\twhile (fileToProcess) {\n\t\t\tconst progressInfo = {\n\t\t\t\tprocessedFilesCount: successes.length + failures.length,\n\t\t\t\testimateFileCount,\n\t\t\t};\n\t\t\tonProgress?.(progressInfo);\n\n\t\t\ttry {\n\t\t\t\tconst result = await processFile(fileToProcess, progressInfo);\n\t\t\t\tsuccesses.push({ filePath: fileToProcess, result });\n\t\t\t} catch (err) {\n\t\t\t\tfailures.push({ filePath: fileToProcess, error: err });\n\t\t\t}\n\n\t\t\tfileToProcess = await getNextFile();\n\t\t}\n\t}));\n\n\tonProgress?.({\n\t\tprocessedFilesCount: successes.length + failures.length,\n\t\testimateFileCount,\n\t});\n\n\treturn {\n\t\tsuccesses,\n\t\tfailures,\n\t};\n};\n","import FormData from 'form-data';\nimport fs from 'fs';\nimport Hash from 'ipfs-only-hash';\nimport fetch from 'node-fetch';\n\nexport type PinataAuth = {\n\tpinataJwtToken: string;\n};\n\nexport type PublishFileResult = {\n\tipfsHash: string;\n};\n\nexport const publishFileToIpfs = async ({\n\tauth,\n\titem,\n}: {\n\tauth: PinataAuth;\n\titem: {\n\t\tname: string;\n\t\tfilePath: string;\n\t};\n}): Promise<PublishFileResult> => {\n\t// The data api to check for existing file is limited to 30 requests per minute\n\t// While uploading allows 180 requests per minute\n\t// i.e. it's faster to just upload again\n\n\t// // Skip if already pinned\n\t// const { isPinned, ipfsHash } = await checkIfFileIsPinned({ auth, item });\n\t// if (isPinned) {\n\t// \treturn {\n\t// \t\tipfsHash,\n\t// \t};\n\t// }\n\n\tconst data = new FormData();\n\tdata.append('file', fs.createReadStream(item.filePath));\n\tdata.append(\n\t\t'pinataMetadata',\n\t\tJSON.stringify({\n\t\t\tname: item.name,\n\t\t}),\n\t);\n\n\tconst response = await fetch(`https://api.pinata.cloud/pinning/pinFileToIPFS`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t\t'Content-Type': `multipart/form-data; boundary=${(data as unknown as { _boundary: string })._boundary}`,\n\t\t},\n\t\tbody: data,\n\t\tmethod: 'post',\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to upload '${item.name}' to ipfs ${response.statusText}`);\n\t}\n\n\tconst uploadResult = await response.json() as {\n\t\tIpfsHash: string; // This is the IPFS multi-hash provided back for your content,\n\t\tPinSize: string; // This is how large (in bytes) the content you just pinned is,\n\t\tTimestamp: string; // This is the timestamp for your content pinning (represented in ISO 8601 format)\n\t};\n\n\treturn {\n\t\tipfsHash: uploadResult.IpfsHash,\n\t};\n};\n\nconst checkIfFileIsPinned = async ({\n\tauth,\n\titem,\n}: {\n\tauth: PinataAuth;\n\titem: {\n\t\tname: string;\n\t\tfilePath: string;\n\t};\n}) => {\n\tconst ipfsHash = await Hash.of(fs.createReadStream(item.filePath));\n\n\tconst response = await fetch(`https://api.pinata.cloud/data/pinList?status=pinned&hashContains=${ipfsHash}`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t},\n\t\tmethod: 'get',\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to query '${item.name}' status from pinata ${response.statusText}`);\n\t}\n\n\tconst pinResult = await response.json() as {\n\t\tcount: number;\n\t\trows: {\n\t\t\tid: string;\n\t\t\tipfs_pin_hash: string;\n\t\t\tsize: number;\n\t\t\tuser_id: string;\n\t\t\tdate_pinned: null | string;\n\t\t\tdate_unpinned: null | string;\n\t\t\tmetadata: {\n\t\t\t\tname: string;\n\t\t\t\tkeyvalues: null | string;\n\t\t\t};\n\t\t\tregions: {\n\t\t\t\tregionId: string;\n\t\t\t\tcurrentReplicationCount: number;\n\t\t\t\tdesiredReplicationCount: number;\n\t\t\t}[];\n\t\t}[];\n\t};\n\n\tconst isPinned = pinResult.rows.some(x =>\n\t\tx.ipfs_pin_hash === ipfsHash\n\t\t&& x.date_pinned\n\t\t&& !x.date_unpinned\n\t);\n\n\treturn {\n\t\tisPinned,\n\t\tipfsHash,\n\t};\n};\n\nexport const pinHash = async ({\n\tauth,\n\tipfsHash,\n}: {\n\tauth: PinataAuth;\n\tipfsHash: string;\n}) => {\n\tconst response = await fetch(`https://api.pinata.cloud/pinning/pinByHash`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t\t'Content-Type': 'application/json',\n\t\t},\n\t\tmethod: 'post',\n\t\tbody: JSON.stringify({\n\t\t\thashToPin: ipfsHash,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to pin '${ipfsHash}' with pinata: ${response.statusText}`);\n\t}\n\n\t// Ok is the only response if successful\n\treturn;\n};\n","export async function delay(timeout: number): Promise<void> {\n\treturn await new Promise(resolve => {\n\t\tsetTimeout(resolve, timeout);\n\t});\n}\n\nexport const createProcessBackoffController = ({\n\tretryCount = 5,\n\ttargetRequestsPerMinute = 180,\n}: {\n\tretryCount?: number;\n\ttargetRequestsPerMinute?: number;\n}) => {\n\tlet averageTimePerRequest = 5000;\n\tlet targetTimePerRequest = 60000 / targetRequestsPerMinute;\n\tlet lastTime = Date.now();\n\n\tconst processWithBackoff = async <TResult>(process: () => Promise<TResult>) => {\n\t\tlet attempt = 0;\n\t\tlet lastError = undefined as unknown;\n\t\twhile (attempt < retryCount) {\n\t\t\ttry {\n\t\t\t\tlet delayTimeMs = Math.max(10, targetTimePerRequest - averageTimePerRequest);\n\n\t\t\t\t// Partially randomized delay to ensure parallel requests don't line up\n\t\t\t\tawait delay(Math.floor(delayTimeMs * (1 + 0.5 * Math.random())));\n\n\t\t\t\tconst result = await process();\n\n\t\t\t\tconst timeNow = Date.now();\n\t\t\t\tconst timeElapsed = timeNow - lastTime;\n\t\t\t\tlastTime = timeNow;\n\n\t\t\t\t// Running average\n\t\t\t\taverageTimePerRequest = averageTimePerRequest * 0.97 + timeElapsed * 0.03;\n\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tlastError = err;\n\t\t\t}\n\n\t\t\t// Quickly increase time to wait if failure (allow negatives to wait longer than target)\n\t\t\taverageTimePerRequest -= (attempt + 1) * 1000;\n\t\t\tattempt++;\n\t\t}\n\n\t\t// All attempts failed\n\t\tthrow lastError;\n\t};\n\n\treturn {\n\t\tprocessWithBackoff,\n\t};\n};\n"],"names":[],"version":3,"file":"index.js.map","sourceRoot":"../"}
1
+ {"version":3,"sources":["index.ts","src/proxy.ts","src/file-processing.ts","src/pinata-api.ts","src/utils.ts"],"sourcesContent":["import { Option, Plugin, PositionalArg, Task } from '@taqueria/node-sdk';\nimport proxy from './src/proxy';\n\nPlugin.create(() => ({\n\tschema: '0.1',\n\tversion: '0.4.0',\n\talias: 'pinata',\n\ttasks: [\n\t\tTask.create({\n\t\t\ttask: 'publish',\n\t\t\tcommand: 'publish [path]',\n\t\t\tdescription: 'Upload and pin files using your pinata account.',\n\t\t\taliases: [],\n\t\t\thandler: 'proxy',\n\t\t\tpositionals: [\n\t\t\t\tPositionalArg.create({\n\t\t\t\t\tplaceholder: 'path',\n\t\t\t\t\tdescription: 'Directory or file path to publish',\n\t\t\t\t\ttype: 'string',\n\t\t\t\t}),\n\t\t\t],\n\t\t\tencoding: 'json',\n\t\t}),\n\t\tTask.create({\n\t\t\ttask: 'pin',\n\t\t\tcommand: 'pin [hash]',\n\t\t\tdescription: 'Pin a file already on ipfs with your pinata account.',\n\t\t\taliases: [],\n\t\t\thandler: 'proxy',\n\t\t\tpositionals: [\n\t\t\t\tPositionalArg.create({\n\t\t\t\t\tplaceholder: 'hash',\n\t\t\t\t\tdescription: 'Ipfs hash of the file or directory that is already on the ipfs network.',\n\t\t\t\t\ttype: 'string',\n\t\t\t\t}),\n\t\t\t],\n\t\t}),\n\t],\n\tproxy,\n}), process.argv);\n","import { sendAsyncErr, sendAsyncRes, sendErr, sendJsonRes } from '@taqueria/node-sdk';\nimport { RequestArgs } from '@taqueria/node-sdk';\nimport { LoadedConfig, SanitizedAbsPath } from '@taqueria/node-sdk/types';\nimport path from 'path';\nimport { processFiles } from './file-processing';\nimport { PinataAuth, pinHash, publishFileToIpfs } from './pinata-api';\nimport { createProcessBackoffController } from './utils';\n\n// Load .env for jwt token\n// TODO: How should this be stored in a secure way?\nimport 'dotenv/config';\n\n// TODO: What should this be, it was removed from the sdk\ntype PluginResponse =\n\t| void\n\t| {\n\t\trender: 'table';\n\t\tdata: unknown[];\n\t};\n\ninterface Opts extends RequestArgs.t {\n\treadonly path?: string;\n\treadonly hash?: string;\n\treadonly task?: string;\n}\n\nconst publishToIpfs = async (fileOrDirPath: undefined | string, auth: PinataAuth): Promise<PluginResponse> => {\n\tif (!fileOrDirPath) {\n\t\tthrow new Error(`path was not provided`);\n\t}\n\n\t// Pinata is limited to 180 requests per minute\n\t// So for the first 180 requests they can go fast\n\n\tconst { processWithBackoff } = createProcessBackoffController({\n\t\tretryCount: 5,\n\t\ttargetRequestsPerMinute: 180,\n\t});\n\n\tconst result = await processFiles({\n\t\tfileOrDirPath,\n\t\tparallelCount: 10,\n\t\tprocessFile: async filePath => {\n\t\t\t// // TEMP: Debug\n\t\t\t// console.log(`publishing: ${filePath}`);\n\n\t\t\treturn processWithBackoff(() =>\n\t\t\t\tpublishFileToIpfs({\n\t\t\t\t\tauth,\n\t\t\t\t\titem: { filePath, name: path.basename(filePath) },\n\t\t\t\t})\n\t\t\t);\n\t\t},\n\t\tonProgress: ({ processedFilesCount, estimateFileCount }) => {\n\t\t\tif (estimateFileCount && processedFilesCount % 10) {\n\t\t\t\tlet ratio = processedFilesCount / estimateFileCount;\n\t\t\t\tif (ratio > 1) ratio = 1;\n\n\t\t\t\t// // TODO: Call task sdk progress\n\t\t\t\t// console.log(`Progress: ${(ratio * 100).toFixed(0)}%`);\n\t\t\t}\n\t\t},\n\t});\n\n\t// // TEMP: DEBUG: Show error\n\t// if (result.failures.length) {\n\t// \tconsole.log('❗ Failures:\\n' + result.failures.map(f => `${f.filePath}: ${f.error}`).join('\\n'));\n\t// }\n\n\treturn {\n\t\trender: 'table',\n\t\tdata: [\n\t\t\t...result.failures.map(x => ({\n\t\t\t\t'?': '❌',\n\t\t\t\tfilePath: x.filePath,\n\t\t\t\tipfsHash: undefined,\n\t\t\t\terror: (x.error as { message?: string })?.message ?? JSON.stringify(x.error),\n\t\t\t})),\n\t\t\t...result.successes.map(x => ({\n\t\t\t\t'?': '✔',\n\t\t\t\tfilePath: x.filePath,\n\t\t\t\tipfsHash: x.result.ipfsHash,\n\t\t\t\terror: undefined,\n\t\t\t})),\n\t\t],\n\t};\n};\n\nconst pinToIpfs = async (hash: undefined | string, auth: PinataAuth): Promise<PluginResponse> => {\n\tif (!hash) {\n\t\tthrow new Error(`ipfs hash was not provided`);\n\t}\n\n\tawait pinHash({ ipfsHash: hash, auth });\n\n\treturn {\n\t\trender: 'table',\n\t\tdata: [{ ipfsHash: hash }],\n\t};\n};\n\nconst execute = async (opts: Opts): Promise<PluginResponse> => {\n\tconst {\n\t\ttask,\n\t\tpath,\n\t\thash,\n\t} = opts;\n\n\tconst auth: PinataAuth = {\n\t\t// TODO: Where should this be stored?\n\t\t// pinataJwtToken: (config as Record<string, any>).credentials.pinataJwtToken,\n\t\tpinataJwtToken: process.env['pinataJwtToken'] as string,\n\t};\n\n\tif (!auth.pinataJwtToken) {\n\t\tthrow new Error(`The 'credentials.pinataJwtToken' was not found in config`);\n\t}\n\n\tswitch (task) {\n\t\tcase 'publish':\n\t\t\treturn publishToIpfs(path, auth);\n\t\tcase 'pin':\n\t\t\treturn pinToIpfs(hash, auth);\n\t\tdefault:\n\t\t\tthrow new Error(`${task} is not an understood task by the ipfs-pinata plugin`);\n\t}\n};\n\nexport default async (args: RequestArgs.t): Promise<PluginResponse> => {\n\tconst opts = args as Opts;\n\n\ttry {\n\t\tconst resultRaw = await execute(opts) as Record<string, unknown>;\n\t\t// TODO: Fix deno parsing\n\t\t// Without this, `data.reduce is not a function`\n\t\tconst result = ('data' in resultRaw) ? resultRaw.data : resultRaw;\n\t\treturn sendJsonRes(result);\n\t} catch (err) {\n\t\tconst error = err as Error;\n\t\tif (error.message) {\n\t\t\treturn sendAsyncErr(error.message);\n\t\t}\n\t}\n};\n","import fs from 'fs/promises';\nimport path from 'path';\n\n// Async generator\n// https://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search\nasync function* getFiles(fileOrDirPath: string): AsyncGenerator<string, void, unknown> {\n\tconst dirInfo = await fs.stat(fileOrDirPath);\n\tif (dirInfo.isFile()) {\n\t\tyield fileOrDirPath;\n\t\treturn;\n\t}\n\n\tconst dirents = await fs.readdir(fileOrDirPath, { withFileTypes: true });\n\tfor (const dirent of dirents) {\n\t\tconst res = path.resolve(fileOrDirPath, dirent.name);\n\t\tif (dirent.isDirectory()) {\n\t\t\tyield* getFiles(res);\n\t\t} else {\n\t\t\tyield res;\n\t\t}\n\t}\n}\n\nconst createFileProvider = async ({\n\tfileOrDirPath,\n\tfilter,\n\tshouldEstimateFileCount,\n}: {\n\tfileOrDirPath: string;\n\tfilter?: (filePath: string) => boolean;\n\tshouldEstimateFileCount?: boolean;\n}) => {\n\tfileOrDirPath = path.resolve(fileOrDirPath);\n\tconst pathInfo = await fs.stat(fileOrDirPath);\n\tif (\n\t\t!pathInfo.isFile()\n\t\t&& !pathInfo.isDirectory()\n\t) {\n\t\tthrow new Error(`The path '${fileOrDirPath}' is not a file or directory`);\n\t}\n\n\tlet estimateFileCount = undefined as undefined | number;\n\tif (shouldEstimateFileCount) {\n\t\testimateFileCount = 0;\n\t\tfor await (const filePath of getFiles(fileOrDirPath)) {\n\t\t\tif (filter && !filter(filePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\testimateFileCount++;\n\t\t}\n\t}\n\n\tconst fileGenerator = getFiles(fileOrDirPath);\n\tconst getNextFile = async () => {\n\t\tlet nextFile = (await fileGenerator.next()).value;\n\t\tif (!filter) {\n\t\t\treturn nextFile;\n\t\t}\n\n\t\twhile (nextFile && !filter(nextFile)) {\n\t\t\tnextFile = await getNextFile();\n\t\t}\n\n\t\treturn nextFile;\n\t};\n\treturn {\n\t\tgetNextFile,\n\t\testimateFileCount,\n\t};\n};\n\ntype ProgressInfo = { processedFilesCount: number; estimateFileCount: undefined | number };\nexport const processFiles = async <TResult>({\n\tfileOrDirPath,\n\tprocessFile,\n\tfilter,\n\tparallelCount = 10,\n\tonProgress,\n}: {\n\tfileOrDirPath: string;\n\tprocessFile: (filePath: string, progress: ProgressInfo) => Promise<TResult>;\n\tfilter?: (filePath: string) => boolean;\n\tparallelCount?: number;\n\tonProgress?: (progress: ProgressInfo) => void;\n}) => {\n\tconst { getNextFile, estimateFileCount } = await createFileProvider({\n\t\tfileOrDirPath,\n\t\tfilter,\n\t\tshouldEstimateFileCount: true,\n\t});\n\n\tconst successes = [] as { filePath: string; result: TResult }[];\n\tconst failures = [] as { filePath: string; error: unknown }[];\n\n\tonProgress?.({\n\t\tprocessedFilesCount: 0,\n\t\testimateFileCount,\n\t});\n\n\tawait Promise.all([...new Array(parallelCount)].map(async x => {\n\t\tlet fileToProcess = await getNextFile();\n\t\twhile (fileToProcess) {\n\t\t\tconst progressInfo = {\n\t\t\t\tprocessedFilesCount: successes.length + failures.length,\n\t\t\t\testimateFileCount,\n\t\t\t};\n\t\t\tonProgress?.(progressInfo);\n\n\t\t\ttry {\n\t\t\t\tconst result = await processFile(fileToProcess, progressInfo);\n\t\t\t\tsuccesses.push({ filePath: fileToProcess, result });\n\t\t\t} catch (err) {\n\t\t\t\tfailures.push({ filePath: fileToProcess, error: err });\n\t\t\t}\n\n\t\t\tfileToProcess = await getNextFile();\n\t\t}\n\t}));\n\n\tonProgress?.({\n\t\tprocessedFilesCount: successes.length + failures.length,\n\t\testimateFileCount,\n\t});\n\n\treturn {\n\t\tsuccesses,\n\t\tfailures,\n\t};\n};\n","import FormData from 'form-data';\nimport fs from 'fs';\nimport Hash from 'ipfs-only-hash';\nimport fetch from 'node-fetch';\n\nexport type PinataAuth = {\n\tpinataJwtToken: string;\n};\n\nexport type PublishFileResult = {\n\tipfsHash: string;\n};\n\nexport const publishFileToIpfs = async ({\n\tauth,\n\titem,\n}: {\n\tauth: PinataAuth;\n\titem: {\n\t\tname: string;\n\t\tfilePath: string;\n\t};\n}): Promise<PublishFileResult> => {\n\t// The data api to check for existing file is limited to 30 requests per minute\n\t// While uploading allows 180 requests per minute\n\t// i.e. it's faster to just upload again\n\n\t// // Skip if already pinned\n\t// const { isPinned, ipfsHash } = await checkIfFileIsPinned({ auth, item });\n\t// if (isPinned) {\n\t// \treturn {\n\t// \t\tipfsHash,\n\t// \t};\n\t// }\n\n\tconst data = new FormData();\n\tdata.append('file', fs.createReadStream(item.filePath));\n\tdata.append(\n\t\t'pinataMetadata',\n\t\tJSON.stringify({\n\t\t\tname: item.name,\n\t\t}),\n\t);\n\n\tconst response = await fetch(`https://api.pinata.cloud/pinning/pinFileToIPFS`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t\t'Content-Type': `multipart/form-data; boundary=${(data as unknown as { _boundary: string })._boundary}`,\n\t\t},\n\t\tbody: data,\n\t\tmethod: 'post',\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to upload '${item.name}' to ipfs ${response.statusText}`);\n\t}\n\n\tconst uploadResult = await response.json() as {\n\t\tIpfsHash: string; // This is the IPFS multi-hash provided back for your content,\n\t\tPinSize: string; // This is how large (in bytes) the content you just pinned is,\n\t\tTimestamp: string; // This is the timestamp for your content pinning (represented in ISO 8601 format)\n\t};\n\n\treturn {\n\t\tipfsHash: uploadResult.IpfsHash,\n\t};\n};\n\nconst checkIfFileIsPinned = async ({\n\tauth,\n\titem,\n}: {\n\tauth: PinataAuth;\n\titem: {\n\t\tname: string;\n\t\tfilePath: string;\n\t};\n}) => {\n\tconst ipfsHash = await Hash.of(fs.createReadStream(item.filePath));\n\n\tconst response = await fetch(`https://api.pinata.cloud/data/pinList?status=pinned&hashContains=${ipfsHash}`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t},\n\t\tmethod: 'get',\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to query '${item.name}' status from pinata ${response.statusText}`);\n\t}\n\n\tconst pinResult = await response.json() as {\n\t\tcount: number;\n\t\trows: {\n\t\t\tid: string;\n\t\t\tipfs_pin_hash: string;\n\t\t\tsize: number;\n\t\t\tuser_id: string;\n\t\t\tdate_pinned: null | string;\n\t\t\tdate_unpinned: null | string;\n\t\t\tmetadata: {\n\t\t\t\tname: string;\n\t\t\t\tkeyvalues: null | string;\n\t\t\t};\n\t\t\tregions: {\n\t\t\t\tregionId: string;\n\t\t\t\tcurrentReplicationCount: number;\n\t\t\t\tdesiredReplicationCount: number;\n\t\t\t}[];\n\t\t}[];\n\t};\n\n\tconst isPinned = pinResult.rows.some(x =>\n\t\tx.ipfs_pin_hash === ipfsHash\n\t\t&& x.date_pinned\n\t\t&& !x.date_unpinned\n\t);\n\n\treturn {\n\t\tisPinned,\n\t\tipfsHash,\n\t};\n};\n\nexport const pinHash = async ({\n\tauth,\n\tipfsHash,\n}: {\n\tauth: PinataAuth;\n\tipfsHash: string;\n}) => {\n\tconst response = await fetch(`https://api.pinata.cloud/pinning/pinByHash`, {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${auth.pinataJwtToken}`,\n\t\t\t'Content-Type': 'application/json',\n\t\t},\n\t\tmethod: 'post',\n\t\tbody: JSON.stringify({\n\t\t\thashToPin: ipfsHash,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to pin '${ipfsHash}' with pinata: ${response.statusText}`);\n\t}\n\n\t// Ok is the only response if successful\n\treturn;\n};\n","export async function delay(timeout: number): Promise<void> {\n\treturn await new Promise(resolve => {\n\t\tsetTimeout(resolve, timeout);\n\t});\n}\n\nexport const createProcessBackoffController = ({\n\tretryCount = 5,\n\ttargetRequestsPerMinute = 180,\n}: {\n\tretryCount?: number;\n\ttargetRequestsPerMinute?: number;\n}) => {\n\tlet averageTimePerRequest = 5000;\n\tlet targetTimePerRequest = 60000 / targetRequestsPerMinute;\n\tlet lastTime = Date.now();\n\n\tconst processWithBackoff = async <TResult>(process: () => Promise<TResult>) => {\n\t\tlet attempt = 0;\n\t\tlet lastError = undefined as unknown;\n\t\twhile (attempt < retryCount) {\n\t\t\ttry {\n\t\t\t\tlet delayTimeMs = Math.max(10, targetTimePerRequest - averageTimePerRequest);\n\n\t\t\t\t// Partially randomized delay to ensure parallel requests don't line up\n\t\t\t\tawait delay(Math.floor(delayTimeMs * (1 + 0.5 * Math.random())));\n\n\t\t\t\tconst result = await process();\n\n\t\t\t\tconst timeNow = Date.now();\n\t\t\t\tconst timeElapsed = timeNow - lastTime;\n\t\t\t\tlastTime = timeNow;\n\n\t\t\t\t// Running average\n\t\t\t\taverageTimePerRequest = averageTimePerRequest * 0.97 + timeElapsed * 0.03;\n\n\t\t\t\treturn result;\n\t\t\t} catch (err) {\n\t\t\t\tlastError = err;\n\t\t\t}\n\n\t\t\t// Quickly increase time to wait if failure (allow negatives to wait longer than target)\n\t\t\taverageTimePerRequest -= (attempt + 1) * 1000;\n\t\t\tattempt++;\n\t\t}\n\n\t\t// All attempts failed\n\t\tthrow lastError;\n\t};\n\n\treturn {\n\t\tprocessWithBackoff,\n\t};\n};\n"],"mappings":"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","names":["path","fs","process","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taqueria/plugin-ipfs-pinata",
3
- "version": "0.25.16-rc",
3
+ "version": "0.25.19-rc",
4
4
  "description": "A plugin for Taqueria providing ipfs publishing and pinning using the Pinata service",
5
5
  "keywords": [
6
6
  "taqueria",
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "scripts": {
25
- "build": "npx tsc -noEmit -p ./tsconfig.json && npx parcel build --no-cache 2>&1"
25
+ "build": "npx tsc -noEmit -p ./tsconfig.json && npx tsup"
26
26
  },
27
27
  "author": "ECAD Labs",
28
28
  "license": "Apache-2.0",
@@ -33,7 +33,7 @@
33
33
  "directory": "taqueria-plugin-ipfs-pinata"
34
34
  },
35
35
  "dependencies": {
36
- "@taqueria/node-sdk": "^0.25.16-rc",
36
+ "@taqueria/node-sdk": "^0.25.19-rc",
37
37
  "dotenv": "^16.0.0",
38
38
  "form-data": "^4.0.0",
39
39
  "ipfs-only-hash": "^4.0.0",
@@ -41,7 +41,23 @@
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node-fetch": "^2.6.1",
44
- "parcel": "^2.8.0",
44
+ "tsup": "^6.1.3",
45
45
  "typescript": "4.7.2"
46
+ },
47
+ "tsup": {
48
+ "entry": [
49
+ "index.ts"
50
+ ],
51
+ "sourcemap": true,
52
+ "target": "node16",
53
+ "outDir": "./",
54
+ "dts": true,
55
+ "clean": false,
56
+ "skipNodeModulesBundle": true,
57
+ "platform": "node",
58
+ "format": [
59
+ "esm",
60
+ "cjs"
61
+ ]
46
62
  }
47
63
  }