@empiricalrun/r2-uploader 0.1.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/CHANGELOG.md +7 -0
- package/README.md +3 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +150 -0
- package/package.json +32 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface FileMap {
|
|
2
|
+
[file: string]: string;
|
|
3
|
+
}
|
|
4
|
+
export declare function uploadDirectory({ sourceDir, fileList, destinationDir, uploadBucket, accountId, accessKeyId, secretAccessKey, }: {
|
|
5
|
+
sourceDir: string;
|
|
6
|
+
fileList?: string[];
|
|
7
|
+
destinationDir: string;
|
|
8
|
+
uploadBucket: string;
|
|
9
|
+
accountId?: string;
|
|
10
|
+
accessKeyId?: string;
|
|
11
|
+
secretAccessKey?: string;
|
|
12
|
+
}): Promise<FileMap>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAuBA,UAAU,OAAO;IACf,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AA+HD,wBAAsB,eAAe,CAAC,EACpC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,SAAS,EACT,WAAW,EACX,eAAe,GAChB,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC,OAAO,CAAC,CAYnB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.uploadDirectory = void 0;
|
|
30
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
31
|
+
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
32
|
+
const async_retry_1 = __importDefault(require("async-retry"));
|
|
33
|
+
const fs = __importStar(require("fs"));
|
|
34
|
+
const md5_1 = __importDefault(require("md5"));
|
|
35
|
+
const path_1 = __importDefault(require("path"));
|
|
36
|
+
const getFileList = (dir) => {
|
|
37
|
+
let files = [];
|
|
38
|
+
const items = fs.readdirSync(dir, {
|
|
39
|
+
withFileTypes: true,
|
|
40
|
+
});
|
|
41
|
+
for (const item of items) {
|
|
42
|
+
const isDir = item.isDirectory();
|
|
43
|
+
const absolutePath = `${dir}/${item.name}`;
|
|
44
|
+
if (isDir) {
|
|
45
|
+
files = [...files, ...getFileList(absolutePath)];
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
files.push(absolutePath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return files;
|
|
52
|
+
};
|
|
53
|
+
const run = async (config) => {
|
|
54
|
+
const map = new Map();
|
|
55
|
+
const urls = {};
|
|
56
|
+
const S3 = new client_s3_1.S3Client({
|
|
57
|
+
region: "auto",
|
|
58
|
+
endpoint: `https://${config.accountId}.r2.cloudflarestorage.com`,
|
|
59
|
+
credentials: {
|
|
60
|
+
accessKeyId: config.accessKeyId,
|
|
61
|
+
secretAccessKey: config.secretAccessKey,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
let files;
|
|
65
|
+
if (config.fileList) {
|
|
66
|
+
files = config.fileList;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
files = getFileList(config.sourceDir);
|
|
70
|
+
}
|
|
71
|
+
await Promise.all(files.map(async (file) => {
|
|
72
|
+
const fileStream = fs.readFileSync(file);
|
|
73
|
+
const fileName = file.replace(config.sourceDir, "");
|
|
74
|
+
const fileKey = path_1.default.join(config.destinationDir !== "" ? config.destinationDir : config.sourceDir, fileName);
|
|
75
|
+
if (fileKey.includes(".gitkeep"))
|
|
76
|
+
return;
|
|
77
|
+
// console.log(fileKey);
|
|
78
|
+
let mimeType = "application/octet-stream";
|
|
79
|
+
try {
|
|
80
|
+
const mime = (await import("mime")).default;
|
|
81
|
+
mimeType = mime.getType(file) || "application/octet-stream";
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
console.warn("Failed to get mime type for file", file, err);
|
|
85
|
+
}
|
|
86
|
+
const uploadParams = {
|
|
87
|
+
Bucket: config.bucket,
|
|
88
|
+
Key: fileKey,
|
|
89
|
+
Body: fileStream,
|
|
90
|
+
ContentLength: fs.statSync(file).size,
|
|
91
|
+
ContentType: mimeType ?? "application/octet-stream",
|
|
92
|
+
};
|
|
93
|
+
const cmd = new client_s3_1.PutObjectCommand(uploadParams);
|
|
94
|
+
const digest = (0, md5_1.default)(fileStream);
|
|
95
|
+
cmd.middlewareStack.add((next) => async (args) => {
|
|
96
|
+
args.request.headers["if-none-match"] = `"${digest}"`;
|
|
97
|
+
return await next(args);
|
|
98
|
+
}, {
|
|
99
|
+
step: "build",
|
|
100
|
+
name: "addETag",
|
|
101
|
+
});
|
|
102
|
+
try {
|
|
103
|
+
await (0, async_retry_1.default)(async () => {
|
|
104
|
+
try {
|
|
105
|
+
const data = await S3.send(cmd);
|
|
106
|
+
// console.log(`R2 Success - ${file}`);
|
|
107
|
+
map.set(file, data);
|
|
108
|
+
const fileUrl = await (0, s3_request_presigner_1.getSignedUrl)(S3, cmd);
|
|
109
|
+
urls[file] = fileUrl;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const error = err;
|
|
113
|
+
if (error["$metadata"]) {
|
|
114
|
+
if (error.$metadata.httpStatusCode !== 412) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
console.log("Error which is not retried", error);
|
|
119
|
+
}
|
|
120
|
+
}, {
|
|
121
|
+
retries: 5,
|
|
122
|
+
factor: 3,
|
|
123
|
+
minTimeout: 1000,
|
|
124
|
+
maxTimeout: 60000,
|
|
125
|
+
randomize: true,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
const error = err;
|
|
130
|
+
console.log(`R2 Error - ${file} ${config.sourceDir} ${config.destinationDir} \nError: ${error}`);
|
|
131
|
+
console.log("Upload response", error.$response, "with status", error.$metadata.httpStatusCode);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}));
|
|
135
|
+
return urls;
|
|
136
|
+
};
|
|
137
|
+
async function uploadDirectory({ sourceDir, fileList, destinationDir, uploadBucket, accountId, accessKeyId, secretAccessKey, }) {
|
|
138
|
+
let config = {
|
|
139
|
+
accountId: accountId || process.env.R2_ACCOUNT_ID,
|
|
140
|
+
accessKeyId: accessKeyId || process.env.R2_ACCESS_KEY_ID,
|
|
141
|
+
secretAccessKey: secretAccessKey || process.env.R2_SECRET_ACCESS_KEY,
|
|
142
|
+
bucket: uploadBucket,
|
|
143
|
+
sourceDir,
|
|
144
|
+
destinationDir,
|
|
145
|
+
fileList,
|
|
146
|
+
};
|
|
147
|
+
const uploadedFiles = await run(config);
|
|
148
|
+
return uploadedFiles;
|
|
149
|
+
}
|
|
150
|
+
exports.uploadDirectory = uploadDirectory;
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@empiricalrun/r2-uploader",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"registry": "https://registry.npmjs.org/",
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/empirical-run/empirical.git"
|
|
12
|
+
},
|
|
13
|
+
"author": "Empirical Team <hey@empirical.run>",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@aws-sdk/client-s3": "^3.614.0",
|
|
16
|
+
"@aws-sdk/s3-request-presigner": "^3.614.0",
|
|
17
|
+
"@types/async-retry": "^1.4.8",
|
|
18
|
+
"@types/md5": "^2.3.5",
|
|
19
|
+
"@types/mime": "3.0.0",
|
|
20
|
+
"async-retry": "^1.3.3",
|
|
21
|
+
"md5": "^2.3.0",
|
|
22
|
+
"mime": "3.0.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"dev": "tsc --build --watch",
|
|
26
|
+
"build": "tsc --build",
|
|
27
|
+
"clean": "tsc --build --clean",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest"
|
|
31
|
+
}
|
|
32
|
+
}
|