@stacksjs/ts-cloud 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/aws/index.js +259 -0
  2. package/dist/bin/cli.js +316 -316
  3. package/dist/chunk-0wxyppza.js +146 -0
  4. package/dist/chunk-3knnr7wh.js +601 -0
  5. package/dist/chunk-3rsfns7x.js +10427 -0
  6. package/dist/chunk-93hjhs78.js +1752 -0
  7. package/dist/chunk-arsh1g5h.js +1749 -0
  8. package/dist/chunk-b82pbxyp.js +1572 -0
  9. package/dist/chunk-c6rgvg1j.js +46124 -0
  10. package/dist/chunk-d2p5n2aq.js +23 -0
  11. package/dist/chunk-d7p84vz5.js +1699 -0
  12. package/dist/chunk-dpkk640m.js +4392 -0
  13. package/dist/chunk-eq08r166.js +8 -0
  14. package/dist/chunk-hsk6fe6x.js +371 -0
  15. package/dist/chunk-mj1tmhte.js +13 -0
  16. package/dist/chunk-p6309384.js +12828 -0
  17. package/dist/chunk-pegd7rmf.js +10 -0
  18. package/dist/chunk-pqfzdg68.js +12 -0
  19. package/dist/chunk-qpj3edwz.js +601 -0
  20. package/dist/chunk-tjjgajbh.js +1032 -0
  21. package/dist/chunk-tnztxpcb.js +630 -0
  22. package/dist/chunk-tt4kxske.js +1788 -0
  23. package/dist/chunk-v0bahtg2.js +4 -0
  24. package/dist/chunk-vd87cpvn.js +1754 -0
  25. package/dist/chunk-zn0nxxa8.js +1779 -0
  26. package/dist/deploy/index.js +87 -0
  27. package/dist/dns/index.js +31 -0
  28. package/dist/drivers/hetzner/provision.d.ts +53 -0
  29. package/dist/drivers/index.d.ts +5 -1
  30. package/dist/drivers/index.js +98 -0
  31. package/dist/drivers/shared/remote-exec.d.ts +47 -0
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +4383 -91358
  34. package/dist/push/index.js +509 -0
  35. package/dist/ui/index.html +3 -3
  36. package/dist/ui/server/actions.html +3 -3
  37. package/dist/ui/server/backups.html +3 -3
  38. package/dist/ui/server/database.html +3 -3
  39. package/dist/ui/server/deployments.html +3 -3
  40. package/dist/ui/server/firewall.html +3 -3
  41. package/dist/ui/server/logs.html +3 -3
  42. package/dist/ui/server/services.html +3 -3
  43. package/dist/ui/server/sites.html +3 -3
  44. package/dist/ui/server/ssh-keys.html +3 -3
  45. package/dist/ui/server/terminal.html +3 -3
  46. package/dist/ui/server/workers.html +3 -3
  47. package/dist/ui/serverless/alarms.html +3 -3
  48. package/dist/ui/serverless/assets.html +3 -3
  49. package/dist/ui/serverless/data.html +3 -3
  50. package/dist/ui/serverless/deployments.html +3 -3
  51. package/dist/ui/serverless/functions.html +3 -3
  52. package/dist/ui/serverless/logs.html +3 -3
  53. package/dist/ui/serverless/queues.html +3 -3
  54. package/dist/ui/serverless/scheduler.html +3 -3
  55. package/dist/ui/serverless/secrets.html +3 -3
  56. package/dist/ui/serverless/traces.html +3 -3
  57. package/dist/ui/serverless.html +3 -3
  58. package/package.json +3 -3
@@ -0,0 +1,1754 @@
1
+ import {
2
+ AWSClient
3
+ } from "./chunk-arsh1g5h.js";
4
+ import {
5
+ __require
6
+ } from "./chunk-v0bahtg2.js";
7
+
8
+ // src/aws/s3.ts
9
+ import * as crypto from "node:crypto";
10
+
11
+ // src/aws/credentials.ts
12
+ function resolveCredentials(profile) {
13
+ if (profile) {
14
+ const creds = loadProfileFromFile(profile);
15
+ if (!creds) {
16
+ throw new Error(`AWS profile '${profile}' not found in ~/.aws/credentials`);
17
+ }
18
+ return creds;
19
+ }
20
+ const envAccessKey = process.env.AWS_ACCESS_KEY_ID;
21
+ const envSecretKey = process.env.AWS_SECRET_ACCESS_KEY;
22
+ if (envAccessKey && envSecretKey) {
23
+ return {
24
+ accessKeyId: envAccessKey,
25
+ secretAccessKey: envSecretKey,
26
+ sessionToken: process.env.AWS_SESSION_TOKEN
27
+ };
28
+ }
29
+ return loadProfileFromFile(process.env.AWS_PROFILE || "default") ?? { accessKeyId: "", secretAccessKey: "" };
30
+ }
31
+ function loadProfileFromFile(profile) {
32
+ const { existsSync, readFileSync } = __require("node:fs");
33
+ const { homedir } = __require("node:os");
34
+ const { join } = __require("node:path");
35
+ const credentialsPath = process.env.AWS_SHARED_CREDENTIALS_FILE || join(homedir(), ".aws", "credentials");
36
+ if (!existsSync(credentialsPath))
37
+ return null;
38
+ const content = readFileSync(credentialsPath, "utf-8");
39
+ let currentProfile = null;
40
+ let accessKeyId;
41
+ let secretAccessKey;
42
+ let sessionToken;
43
+ for (const line of content.split(`
44
+ `)) {
45
+ const trimmed = line.trim();
46
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";"))
47
+ continue;
48
+ const profileMatch = trimmed.match(/^\[([^\]]+)\]$/);
49
+ if (profileMatch) {
50
+ if (currentProfile === profile && accessKeyId && secretAccessKey) {
51
+ return { accessKeyId, secretAccessKey, sessionToken };
52
+ }
53
+ currentProfile = profileMatch[1];
54
+ accessKeyId = undefined;
55
+ secretAccessKey = undefined;
56
+ sessionToken = undefined;
57
+ continue;
58
+ }
59
+ if (currentProfile === profile) {
60
+ const [key, ...valueParts] = trimmed.split("=");
61
+ const value = valueParts.join("=").trim();
62
+ switch (key.trim().toLowerCase()) {
63
+ case "aws_access_key_id":
64
+ accessKeyId = value;
65
+ break;
66
+ case "aws_secret_access_key":
67
+ secretAccessKey = value;
68
+ break;
69
+ case "aws_session_token":
70
+ sessionToken = value;
71
+ break;
72
+ }
73
+ }
74
+ }
75
+ if (currentProfile === profile && accessKeyId && secretAccessKey) {
76
+ return { accessKeyId, secretAccessKey, sessionToken };
77
+ }
78
+ return null;
79
+ }
80
+
81
+ // src/aws/s3.ts
82
+ import { readdir } from "node:fs/promises";
83
+ import { join } from "node:path";
84
+ import { readFileSync } from "node:fs";
85
+ function toFetchBody(data) {
86
+ const { buffer, byteOffset, byteLength } = data;
87
+ if (buffer instanceof ArrayBuffer) {
88
+ return buffer.slice(byteOffset, byteOffset + byteLength);
89
+ }
90
+ const copy = new ArrayBuffer(byteLength);
91
+ new Uint8Array(copy).set(new Uint8Array(data));
92
+ return copy;
93
+ }
94
+
95
+ class S3Client {
96
+ client;
97
+ region;
98
+ explicitProfile;
99
+ endpoint;
100
+ forcePathStyle;
101
+ explicitCredentials;
102
+ constructor(region = "us-east-1", profile, options) {
103
+ this.region = region;
104
+ this.explicitProfile = profile;
105
+ this.endpoint = options?.endpoint;
106
+ this.forcePathStyle = options?.forcePathStyle;
107
+ this.explicitCredentials = options?.credentials;
108
+ this.client = new AWSClient(options?.credentials, { profile, endpoint: options?.endpoint, forcePathStyle: options?.forcePathStyle });
109
+ }
110
+ getCredentials() {
111
+ if (this.explicitCredentials?.accessKeyId && this.explicitCredentials.secretAccessKey) {
112
+ return this.explicitCredentials;
113
+ }
114
+ const creds = resolveCredentials(this.explicitProfile);
115
+ if (creds.accessKeyId && creds.secretAccessKey) {
116
+ return creds;
117
+ }
118
+ throw new Error("S3 credentials not found. Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (or pass explicit credentials/profile), or configure ~/.aws/credentials.");
119
+ }
120
+ s3BaseHost() {
121
+ return this.endpoint || `s3.${this.region}.amazonaws.com`;
122
+ }
123
+ s3VirtualHost(bucket) {
124
+ return this.forcePathStyle ? this.s3BaseHost() : `${bucket}.${this.s3BaseHost()}`;
125
+ }
126
+ async listBuckets() {
127
+ const result = await this.client.request({
128
+ service: "s3",
129
+ region: this.region,
130
+ method: "GET",
131
+ path: "/"
132
+ });
133
+ const buckets = [];
134
+ const root = result?.ListAllMyBucketsResult ?? result;
135
+ const bucketList = root?.Buckets?.Bucket;
136
+ if (bucketList) {
137
+ const list = Array.isArray(bucketList) ? bucketList : [bucketList];
138
+ for (const b of list) {
139
+ buckets.push({
140
+ Name: b.Name,
141
+ CreationDate: b.CreationDate
142
+ });
143
+ }
144
+ }
145
+ return { Buckets: buckets };
146
+ }
147
+ async createBucket(bucket, options) {
148
+ const headers = {};
149
+ if (options?.acl) {
150
+ headers["x-amz-acl"] = options.acl;
151
+ }
152
+ let body;
153
+ if (this.region !== "us-east-1" && !this.endpoint) {
154
+ body = `<?xml version="1.0" encoding="UTF-8"?>
155
+ <CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
156
+ <LocationConstraint>${this.region}</LocationConstraint>
157
+ </CreateBucketConfiguration>`;
158
+ headers["Content-Type"] = "application/xml";
159
+ }
160
+ await this.client.request({
161
+ service: "s3",
162
+ region: this.region,
163
+ method: "PUT",
164
+ path: `/${bucket}`,
165
+ headers,
166
+ body
167
+ });
168
+ }
169
+ async deleteBucket(bucket) {
170
+ await this.client.request({
171
+ service: "s3",
172
+ region: this.region,
173
+ method: "DELETE",
174
+ path: `/${bucket}`
175
+ });
176
+ }
177
+ async emptyAndDeleteBucket(bucket) {
178
+ let hasMore = true;
179
+ while (hasMore) {
180
+ const objects = await this.listAllObjects({ bucket });
181
+ if (objects.length === 0) {
182
+ hasMore = false;
183
+ break;
184
+ }
185
+ const keys = objects.map((obj) => obj.Key);
186
+ for (let i = 0;i < keys.length; i += 1000) {
187
+ const batch = keys.slice(i, i + 1000);
188
+ await this.deleteObjects(bucket, batch);
189
+ }
190
+ }
191
+ await this.deleteBucket(bucket);
192
+ }
193
+ async listAllObjects(options) {
194
+ const allObjects = [];
195
+ let continuationToken;
196
+ do {
197
+ const params = {
198
+ "list-type": "2",
199
+ "max-keys": "1000"
200
+ };
201
+ if (options.prefix) {
202
+ params.prefix = options.prefix;
203
+ }
204
+ if (continuationToken) {
205
+ params["continuation-token"] = continuationToken;
206
+ }
207
+ const result = await this.client.request({
208
+ service: "s3",
209
+ region: this.region,
210
+ method: "GET",
211
+ path: `/${options.bucket}`,
212
+ queryParams: params
213
+ });
214
+ const root = result?.ListBucketResult ?? result;
215
+ const contents = root?.Contents;
216
+ if (contents) {
217
+ const list = Array.isArray(contents) ? contents : [contents];
218
+ for (const obj of list) {
219
+ allObjects.push({
220
+ Key: obj.Key,
221
+ LastModified: obj.LastModified || "",
222
+ Size: Number.parseInt(obj.Size || "0"),
223
+ ETag: obj.ETag
224
+ });
225
+ }
226
+ }
227
+ const isTruncated = root?.IsTruncated;
228
+ continuationToken = isTruncated === "true" || isTruncated === true ? root?.NextContinuationToken : undefined;
229
+ } while (continuationToken);
230
+ return allObjects;
231
+ }
232
+ async list(options) {
233
+ const result = await this.client.request({
234
+ service: "s3",
235
+ region: this.region,
236
+ method: "GET",
237
+ path: `/${options.bucket}`
238
+ });
239
+ const objects = [];
240
+ const root = result?.ListBucketResult ?? result;
241
+ const contents = root?.Contents;
242
+ if (contents) {
243
+ const items = Array.isArray(contents) ? contents : [contents];
244
+ for (const item of items) {
245
+ if (options.prefix && !item.Key?.startsWith(options.prefix)) {
246
+ continue;
247
+ }
248
+ objects.push({
249
+ Key: item.Key || "",
250
+ LastModified: item.LastModified || "",
251
+ Size: Number.parseInt(item.Size || "0"),
252
+ ETag: item.ETag
253
+ });
254
+ if (options.maxKeys && objects.length >= options.maxKeys) {
255
+ break;
256
+ }
257
+ }
258
+ }
259
+ return objects;
260
+ }
261
+ async putObject(options) {
262
+ const headers = {};
263
+ if (options.acl) {
264
+ headers["x-amz-acl"] = options.acl;
265
+ }
266
+ if (options.cacheControl) {
267
+ headers["Cache-Control"] = options.cacheControl;
268
+ }
269
+ if (options.contentType) {
270
+ headers["Content-Type"] = options.contentType;
271
+ }
272
+ if (options.metadata) {
273
+ for (const [key, value] of Object.entries(options.metadata)) {
274
+ headers[`x-amz-meta-${key}`] = value;
275
+ }
276
+ }
277
+ const encodedKey = options.key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
278
+ const normalizedBody = options.body instanceof Uint8Array && !Buffer.isBuffer(options.body) ? Buffer.from(options.body) : options.body;
279
+ if (Buffer.isBuffer(normalizedBody) || normalizedBody instanceof Uint8Array) {
280
+ const binaryBody = Buffer.isBuffer(normalizedBody) ? normalizedBody : Buffer.from(normalizedBody);
281
+ const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
282
+ const host = this.s3VirtualHost(options.bucket);
283
+ const url = `https://${host}/${encodedKey}`;
284
+ const now = new Date;
285
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
286
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
287
+ const payloadHash = crypto.createHash("sha256").update(binaryBody).digest("hex");
288
+ const requestHeaders = {
289
+ host,
290
+ "x-amz-date": amzDate,
291
+ "x-amz-content-sha256": payloadHash,
292
+ ...headers
293
+ };
294
+ if (sessionToken) {
295
+ requestHeaders["x-amz-security-token"] = sessionToken;
296
+ }
297
+ const canonicalHeaders = Object.keys(requestHeaders).sort().map((key) => `${key.toLowerCase()}:${requestHeaders[key].trim()}
298
+ `).join("");
299
+ const signedHeaders = Object.keys(requestHeaders).sort().map((key) => key.toLowerCase()).join(";");
300
+ const canonicalRequest = [
301
+ "PUT",
302
+ `/${encodedKey}`,
303
+ "",
304
+ canonicalHeaders,
305
+ signedHeaders,
306
+ payloadHash
307
+ ].join(`
308
+ `);
309
+ const algorithm = "AWS4-HMAC-SHA256";
310
+ const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
311
+ const stringToSign = [
312
+ algorithm,
313
+ amzDate,
314
+ credentialScope,
315
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex")
316
+ ].join(`
317
+ `);
318
+ const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
319
+ const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
320
+ const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
321
+ const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
322
+ const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
323
+ const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
324
+ const response = await fetch(url, {
325
+ method: "PUT",
326
+ headers: {
327
+ ...requestHeaders,
328
+ Authorization: authorizationHeader
329
+ },
330
+ body: toFetchBody(binaryBody)
331
+ });
332
+ if (!response.ok) {
333
+ const errorText = await response.text();
334
+ throw new Error(`S3 PUT failed: ${response.status} ${errorText}`);
335
+ }
336
+ return;
337
+ }
338
+ await this.client.request({
339
+ service: "s3",
340
+ region: this.region,
341
+ method: "PUT",
342
+ path: `/${encodedKey}`,
343
+ bucket: options.bucket,
344
+ headers,
345
+ body: options.body
346
+ });
347
+ }
348
+ async getObject(bucket, key) {
349
+ const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
350
+ const result = await this.client.request({
351
+ service: "s3",
352
+ region: this.region,
353
+ method: "GET",
354
+ path: `/${bucket}/${encodedKey}`,
355
+ rawResponse: true
356
+ });
357
+ return result;
358
+ }
359
+ async getObjectBytes(bucket, key) {
360
+ const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
361
+ const host = this.s3VirtualHost(bucket);
362
+ const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
363
+ const canonicalUri = this.forcePathStyle ? `/${bucket}/${encodedKey}` : `/${encodedKey}`;
364
+ const url = `https://${host}${canonicalUri}`;
365
+ const now = new Date;
366
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
367
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
368
+ const payloadHash = crypto.createHash("sha256").update("").digest("hex");
369
+ const requestHeaders = {
370
+ host,
371
+ "x-amz-date": amzDate,
372
+ "x-amz-content-sha256": payloadHash
373
+ };
374
+ if (sessionToken) {
375
+ requestHeaders["x-amz-security-token"] = sessionToken;
376
+ }
377
+ const canonicalHeaders = Object.keys(requestHeaders).sort().map((k) => `${k.toLowerCase()}:${requestHeaders[k].trim()}
378
+ `).join("");
379
+ const signedHeaders = Object.keys(requestHeaders).sort().map((k) => k.toLowerCase()).join(";");
380
+ const canonicalRequest = [
381
+ "GET",
382
+ canonicalUri,
383
+ "",
384
+ canonicalHeaders,
385
+ signedHeaders,
386
+ payloadHash
387
+ ].join(`
388
+ `);
389
+ const algorithm = "AWS4-HMAC-SHA256";
390
+ const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
391
+ const stringToSign = [
392
+ algorithm,
393
+ amzDate,
394
+ credentialScope,
395
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex")
396
+ ].join(`
397
+ `);
398
+ const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
399
+ const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
400
+ const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
401
+ const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
402
+ const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
403
+ const authorizationHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
404
+ const response = await fetch(url, {
405
+ method: "GET",
406
+ headers: { ...requestHeaders, Authorization: authorizationHeader }
407
+ });
408
+ if (!response.ok) {
409
+ const errorText = await response.text();
410
+ throw new Error(`S3 GET failed: ${response.status} ${errorText}`);
411
+ }
412
+ const buffer = await response.arrayBuffer();
413
+ const contentLengthHeader = response.headers.get("content-length");
414
+ return {
415
+ body: new Uint8Array(buffer),
416
+ contentType: response.headers.get("content-type") ?? undefined,
417
+ contentLength: contentLengthHeader ? Number.parseInt(contentLengthHeader, 10) : undefined
418
+ };
419
+ }
420
+ async copyObject(options) {
421
+ const headers = {
422
+ "x-amz-copy-source": `/${options.sourceBucket}/${options.sourceKey}`
423
+ };
424
+ if (options.metadataDirective) {
425
+ headers["x-amz-metadata-directive"] = options.metadataDirective;
426
+ }
427
+ if (options.contentType) {
428
+ headers["Content-Type"] = options.contentType;
429
+ }
430
+ if (options.metadata) {
431
+ for (const [key, value] of Object.entries(options.metadata)) {
432
+ headers[`x-amz-meta-${key}`] = value;
433
+ }
434
+ }
435
+ await this.client.request({
436
+ service: "s3",
437
+ region: this.region,
438
+ method: "PUT",
439
+ path: `/${options.destinationBucket}/${options.destinationKey}`,
440
+ headers
441
+ });
442
+ }
443
+ async deleteObject(bucket, key) {
444
+ const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
445
+ await this.client.request({
446
+ service: "s3",
447
+ region: this.region,
448
+ method: "DELETE",
449
+ path: `/${bucket}/${encodedKey}`
450
+ });
451
+ }
452
+ async deleteObjects(bucket, keys) {
453
+ const deleteXml = `<?xml version="1.0" encoding="UTF-8"?>
454
+ <Delete>
455
+ ${keys.map((key) => `<Object><Key>${key}</Key></Object>`).join(`
456
+ `)}
457
+ </Delete>`;
458
+ const contentMd5 = crypto.createHash("md5").update(deleteXml).digest("base64");
459
+ await this.client.request({
460
+ service: "s3",
461
+ region: this.region,
462
+ method: "POST",
463
+ path: `/${bucket}`,
464
+ queryParams: { delete: "" },
465
+ body: deleteXml,
466
+ headers: {
467
+ "Content-Type": "application/xml",
468
+ "Content-MD5": contentMd5
469
+ }
470
+ });
471
+ }
472
+ async bucketExists(bucket) {
473
+ try {
474
+ await this.client.request({
475
+ service: "s3",
476
+ region: this.region,
477
+ method: "HEAD",
478
+ path: `/${bucket}`
479
+ });
480
+ return true;
481
+ } catch {
482
+ return false;
483
+ }
484
+ }
485
+ async copy(options) {
486
+ const fileContent = readFileSync(options.source);
487
+ await this.putObject({
488
+ bucket: options.bucket,
489
+ key: options.key,
490
+ body: fileContent,
491
+ acl: options.acl,
492
+ cacheControl: options.cacheControl,
493
+ contentType: options.contentType,
494
+ metadata: options.metadata
495
+ });
496
+ }
497
+ async sync(options) {
498
+ const files = await this.listFilesRecursive(options.source);
499
+ for (const file of files) {
500
+ if (options.exclude && options.exclude.some((pattern) => file.includes(pattern))) {
501
+ continue;
502
+ }
503
+ if (options.include && !options.include.some((pattern) => file.includes(pattern))) {
504
+ continue;
505
+ }
506
+ const relativePath = file.substring(options.source.length + 1);
507
+ const s3Key = options.prefix ? `${options.prefix}/${relativePath}` : relativePath;
508
+ if (!options.dryRun) {
509
+ const fileContent = readFileSync(file);
510
+ await this.putObject({
511
+ bucket: options.bucket,
512
+ key: s3Key,
513
+ body: fileContent,
514
+ acl: options.acl,
515
+ cacheControl: options.cacheControl,
516
+ contentType: options.contentType,
517
+ metadata: options.metadata
518
+ });
519
+ }
520
+ }
521
+ }
522
+ async delete(bucket, key) {
523
+ await this.deleteObject(bucket, key);
524
+ }
525
+ async deletePrefix(bucket, prefix) {
526
+ const objects = await this.list({ bucket, prefix });
527
+ const keys = objects.map((obj) => obj.Key);
528
+ if (keys.length > 0) {
529
+ await this.deleteObjects(bucket, keys);
530
+ }
531
+ }
532
+ async getBucketSize(bucket, prefix) {
533
+ const objects = await this.list({ bucket, prefix });
534
+ return objects.reduce((total, obj) => total + obj.Size, 0);
535
+ }
536
+ async listFilesRecursive(dir) {
537
+ const files = [];
538
+ const entries = await readdir(dir, { withFileTypes: true });
539
+ for (const entry of entries) {
540
+ const fullPath = join(dir, entry.name);
541
+ if (entry.isDirectory()) {
542
+ const subFiles = await this.listFilesRecursive(fullPath);
543
+ files.push(...subFiles);
544
+ } else {
545
+ files.push(fullPath);
546
+ }
547
+ }
548
+ return files;
549
+ }
550
+ async putBucketPolicy(bucket, policy) {
551
+ const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
552
+ const host = this.s3BaseHost();
553
+ const policyString = typeof policy === "string" ? policy : JSON.stringify(policy);
554
+ const now = new Date;
555
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
556
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
557
+ const payloadHash = crypto.createHash("sha256").update(policyString).digest("hex");
558
+ const canonicalUri = "/" + bucket;
559
+ const canonicalQuerystring = "policy=";
560
+ const requestHeaders = {
561
+ host,
562
+ "x-amz-date": amzDate,
563
+ "x-amz-content-sha256": payloadHash,
564
+ "content-type": "application/json"
565
+ };
566
+ if (sessionToken) {
567
+ requestHeaders["x-amz-security-token"] = sessionToken;
568
+ }
569
+ const canonicalHeaders = Object.keys(requestHeaders).sort().map((key) => `${key.toLowerCase()}:${requestHeaders[key].trim()}
570
+ `).join("");
571
+ const signedHeaders = Object.keys(requestHeaders).sort().map((key) => key.toLowerCase()).join(";");
572
+ const canonicalRequest = [
573
+ "PUT",
574
+ canonicalUri,
575
+ canonicalQuerystring,
576
+ canonicalHeaders,
577
+ signedHeaders,
578
+ payloadHash
579
+ ].join(`
580
+ `);
581
+ const algorithm = "AWS4-HMAC-SHA256";
582
+ const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
583
+ const stringToSign = [
584
+ algorithm,
585
+ amzDate,
586
+ credentialScope,
587
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex")
588
+ ].join(`
589
+ `);
590
+ const kDate = crypto.createHmac("sha256", "AWS4" + secretAccessKey).update(dateStamp).digest();
591
+ const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
592
+ const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
593
+ const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
594
+ const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
595
+ const authHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
596
+ const url = `https://${host}${canonicalUri}?${canonicalQuerystring}`;
597
+ const response = await fetch(url, {
598
+ method: "PUT",
599
+ headers: {
600
+ ...requestHeaders,
601
+ Authorization: authHeader
602
+ },
603
+ body: policyString
604
+ });
605
+ if (!response.ok) {
606
+ const text = await response.text();
607
+ throw new Error(`Failed to put bucket policy: ${response.status} ${text}`);
608
+ }
609
+ }
610
+ async getBucketPolicy(bucket) {
611
+ const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
612
+ const host = this.s3BaseHost();
613
+ const now = new Date;
614
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
615
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
616
+ const payloadHash = crypto.createHash("sha256").update("").digest("hex");
617
+ const canonicalUri = "/" + bucket;
618
+ const canonicalQuerystring = "policy=";
619
+ const requestHeaders = {
620
+ host,
621
+ "x-amz-date": amzDate,
622
+ "x-amz-content-sha256": payloadHash
623
+ };
624
+ if (sessionToken) {
625
+ requestHeaders["x-amz-security-token"] = sessionToken;
626
+ }
627
+ const canonicalHeaders = Object.keys(requestHeaders).sort().map((key) => `${key.toLowerCase()}:${requestHeaders[key].trim()}
628
+ `).join("");
629
+ const signedHeaders = Object.keys(requestHeaders).sort().map((key) => key.toLowerCase()).join(";");
630
+ const canonicalRequest = [
631
+ "GET",
632
+ canonicalUri,
633
+ canonicalQuerystring,
634
+ canonicalHeaders,
635
+ signedHeaders,
636
+ payloadHash
637
+ ].join(`
638
+ `);
639
+ const algorithm = "AWS4-HMAC-SHA256";
640
+ const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
641
+ const stringToSign = [
642
+ algorithm,
643
+ amzDate,
644
+ credentialScope,
645
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex")
646
+ ].join(`
647
+ `);
648
+ const kDate = crypto.createHmac("sha256", "AWS4" + secretAccessKey).update(dateStamp).digest();
649
+ const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
650
+ const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
651
+ const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
652
+ const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
653
+ const authHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
654
+ const url = `https://${host}${canonicalUri}?${canonicalQuerystring}`;
655
+ const response = await fetch(url, {
656
+ method: "GET",
657
+ headers: {
658
+ ...requestHeaders,
659
+ Authorization: authHeader
660
+ }
661
+ });
662
+ if (response.status === 404) {
663
+ return null;
664
+ }
665
+ if (!response.ok) {
666
+ const text2 = await response.text();
667
+ throw new Error(`Failed to get bucket policy: ${response.status} ${text2}`);
668
+ }
669
+ const text = await response.text();
670
+ return JSON.parse(text);
671
+ }
672
+ async deleteBucketPolicy(bucket) {
673
+ await this.client.request({
674
+ service: "s3",
675
+ region: this.region,
676
+ method: "DELETE",
677
+ path: `/${bucket}`,
678
+ queryParams: { policy: "" }
679
+ });
680
+ }
681
+ async headBucket(bucket) {
682
+ try {
683
+ const result = await this.client.request({
684
+ service: "s3",
685
+ region: this.region,
686
+ method: "HEAD",
687
+ path: `/${bucket}`,
688
+ returnHeaders: true
689
+ });
690
+ return { exists: true, region: result?.headers?.["x-amz-bucket-region"] };
691
+ } catch (e) {
692
+ if (e.statusCode === 404) {
693
+ return { exists: false };
694
+ }
695
+ throw e;
696
+ }
697
+ }
698
+ async headObject(bucket, key) {
699
+ try {
700
+ const result = await this.client.request({
701
+ service: "s3",
702
+ region: this.region,
703
+ method: "HEAD",
704
+ path: `/${bucket}/${key}`,
705
+ returnHeaders: true
706
+ });
707
+ return {
708
+ ContentLength: result?.headers?.["content-length"] ? parseInt(result.headers["content-length"]) : undefined,
709
+ ContentType: result?.headers?.["content-type"],
710
+ ETag: result?.headers?.["etag"],
711
+ LastModified: result?.headers?.["last-modified"]
712
+ };
713
+ } catch (e) {
714
+ if (e.statusCode === 404) {
715
+ return null;
716
+ }
717
+ throw e;
718
+ }
719
+ }
720
+ async getObjectBuffer(bucket, key) {
721
+ const content = await this.getObject(bucket, key);
722
+ return Buffer.from(content);
723
+ }
724
+ async getObjectJson(bucket, key) {
725
+ const content = await this.getObject(bucket, key);
726
+ return JSON.parse(content);
727
+ }
728
+ async putObjectJson(bucket, key, data, options) {
729
+ await this.putObject({
730
+ bucket,
731
+ key,
732
+ body: JSON.stringify(data),
733
+ contentType: "application/json",
734
+ ...options
735
+ });
736
+ }
737
+ async getBucketVersioning(bucket) {
738
+ const result = await this.client.request({
739
+ service: "s3",
740
+ region: this.region,
741
+ method: "GET",
742
+ path: `/${bucket}`,
743
+ queryParams: { versioning: "" }
744
+ });
745
+ return {
746
+ Status: result?.VersioningConfiguration?.Status
747
+ };
748
+ }
749
+ async putBucketVersioning(bucket, status) {
750
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
751
+ <VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
752
+ <Status>${status}</Status>
753
+ </VersioningConfiguration>`;
754
+ await this.client.request({
755
+ service: "s3",
756
+ region: this.region,
757
+ method: "PUT",
758
+ path: `/${bucket}`,
759
+ queryParams: { versioning: "" },
760
+ headers: { "Content-Type": "application/xml" },
761
+ body
762
+ });
763
+ }
764
+ async getBucketLifecycleConfiguration(bucket) {
765
+ try {
766
+ const result = await this.client.request({
767
+ service: "s3",
768
+ region: this.region,
769
+ method: "GET",
770
+ path: `/${bucket}`,
771
+ queryParams: { lifecycle: "" }
772
+ });
773
+ return result?.LifecycleConfiguration;
774
+ } catch (e) {
775
+ if (e.statusCode === 404) {
776
+ return null;
777
+ }
778
+ throw e;
779
+ }
780
+ }
781
+ async putBucketLifecycleConfiguration(bucket, rules) {
782
+ const rulesXml = rules.map((rule) => {
783
+ let ruleXml = `<Rule><ID>${rule.ID}</ID><Status>${rule.Status}</Status>`;
784
+ if (rule.Filter) {
785
+ ruleXml += `<Filter><Prefix>${rule.Filter.Prefix || ""}</Prefix></Filter>`;
786
+ } else {
787
+ ruleXml += "<Filter><Prefix></Prefix></Filter>";
788
+ }
789
+ if (rule.Expiration) {
790
+ if (rule.Expiration.Days) {
791
+ ruleXml += `<Expiration><Days>${rule.Expiration.Days}</Days></Expiration>`;
792
+ } else if (rule.Expiration.Date) {
793
+ ruleXml += `<Expiration><Date>${rule.Expiration.Date}</Date></Expiration>`;
794
+ }
795
+ }
796
+ if (rule.Transitions) {
797
+ for (const t of rule.Transitions) {
798
+ ruleXml += `<Transition><Days>${t.Days}</Days><StorageClass>${t.StorageClass}</StorageClass></Transition>`;
799
+ }
800
+ }
801
+ if (rule.NoncurrentVersionExpiration) {
802
+ ruleXml += `<NoncurrentVersionExpiration><NoncurrentDays>${rule.NoncurrentVersionExpiration.NoncurrentDays}</NoncurrentDays></NoncurrentVersionExpiration>`;
803
+ }
804
+ ruleXml += "</Rule>";
805
+ return ruleXml;
806
+ }).join("");
807
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
808
+ <LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
809
+ ${rulesXml}
810
+ </LifecycleConfiguration>`;
811
+ await this.client.request({
812
+ service: "s3",
813
+ region: this.region,
814
+ method: "PUT",
815
+ path: `/${bucket}`,
816
+ queryParams: { lifecycle: "" },
817
+ headers: { "Content-Type": "application/xml" },
818
+ body
819
+ });
820
+ }
821
+ async deleteBucketLifecycleConfiguration(bucket) {
822
+ await this.client.request({
823
+ service: "s3",
824
+ region: this.region,
825
+ method: "DELETE",
826
+ path: `/${bucket}`,
827
+ queryParams: { lifecycle: "" }
828
+ });
829
+ }
830
+ async getBucketCors(bucket) {
831
+ try {
832
+ const result = await this.client.request({
833
+ service: "s3",
834
+ region: this.region,
835
+ method: "GET",
836
+ path: `/${bucket}`,
837
+ queryParams: { cors: "" }
838
+ });
839
+ return result?.CORSConfiguration;
840
+ } catch (e) {
841
+ if (e.statusCode === 404) {
842
+ return null;
843
+ }
844
+ throw e;
845
+ }
846
+ }
847
+ async putBucketCors(bucket, rules) {
848
+ const rulesXml = rules.map((rule) => {
849
+ let ruleXml = "<CORSRule>";
850
+ for (const origin of rule.AllowedOrigins) {
851
+ ruleXml += `<AllowedOrigin>${origin}</AllowedOrigin>`;
852
+ }
853
+ for (const method of rule.AllowedMethods) {
854
+ ruleXml += `<AllowedMethod>${method}</AllowedMethod>`;
855
+ }
856
+ if (rule.AllowedHeaders) {
857
+ for (const header of rule.AllowedHeaders) {
858
+ ruleXml += `<AllowedHeader>${header}</AllowedHeader>`;
859
+ }
860
+ }
861
+ if (rule.ExposeHeaders) {
862
+ for (const header of rule.ExposeHeaders) {
863
+ ruleXml += `<ExposeHeader>${header}</ExposeHeader>`;
864
+ }
865
+ }
866
+ if (rule.MaxAgeSeconds) {
867
+ ruleXml += `<MaxAgeSeconds>${rule.MaxAgeSeconds}</MaxAgeSeconds>`;
868
+ }
869
+ ruleXml += "</CORSRule>";
870
+ return ruleXml;
871
+ }).join("");
872
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
873
+ <CORSConfiguration>
874
+ ${rulesXml}
875
+ </CORSConfiguration>`;
876
+ await this.client.request({
877
+ service: "s3",
878
+ region: this.region,
879
+ method: "PUT",
880
+ path: `/${bucket}`,
881
+ queryParams: { cors: "" },
882
+ headers: { "Content-Type": "application/xml" },
883
+ body
884
+ });
885
+ }
886
+ async deleteBucketCors(bucket) {
887
+ await this.client.request({
888
+ service: "s3",
889
+ region: this.region,
890
+ method: "DELETE",
891
+ path: `/${bucket}`,
892
+ queryParams: { cors: "" }
893
+ });
894
+ }
895
+ async getBucketEncryption(bucket) {
896
+ try {
897
+ const result = await this.client.request({
898
+ service: "s3",
899
+ region: this.region,
900
+ method: "GET",
901
+ path: `/${bucket}`,
902
+ queryParams: { encryption: "" }
903
+ });
904
+ return result?.ServerSideEncryptionConfiguration;
905
+ } catch (e) {
906
+ if (e.statusCode === 404) {
907
+ return null;
908
+ }
909
+ throw e;
910
+ }
911
+ }
912
+ async putBucketEncryption(bucket, sseAlgorithm, kmsKeyId) {
913
+ let ruleXml = `<ApplyServerSideEncryptionByDefault><SSEAlgorithm>${sseAlgorithm}</SSEAlgorithm>`;
914
+ if (kmsKeyId) {
915
+ ruleXml += `<KMSMasterKeyID>${kmsKeyId}</KMSMasterKeyID>`;
916
+ }
917
+ ruleXml += "</ApplyServerSideEncryptionByDefault>";
918
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
919
+ <ServerSideEncryptionConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
920
+ <Rule>${ruleXml}</Rule>
921
+ </ServerSideEncryptionConfiguration>`;
922
+ await this.client.request({
923
+ service: "s3",
924
+ region: this.region,
925
+ method: "PUT",
926
+ path: `/${bucket}`,
927
+ queryParams: { encryption: "" },
928
+ headers: { "Content-Type": "application/xml" },
929
+ body
930
+ });
931
+ }
932
+ async deleteBucketEncryption(bucket) {
933
+ await this.client.request({
934
+ service: "s3",
935
+ region: this.region,
936
+ method: "DELETE",
937
+ path: `/${bucket}`,
938
+ queryParams: { encryption: "" }
939
+ });
940
+ }
941
+ async getBucketTagging(bucket) {
942
+ try {
943
+ const result = await this.client.request({
944
+ service: "s3",
945
+ region: this.region,
946
+ method: "GET",
947
+ path: `/${bucket}`,
948
+ queryParams: { tagging: "" }
949
+ });
950
+ const tagSet = result?.Tagging?.TagSet?.Tag;
951
+ if (!tagSet)
952
+ return [];
953
+ return Array.isArray(tagSet) ? tagSet : [tagSet];
954
+ } catch (e) {
955
+ if (e.statusCode === 404) {
956
+ return [];
957
+ }
958
+ throw e;
959
+ }
960
+ }
961
+ async putBucketTagging(bucket, tags) {
962
+ const tagsXml = tags.map((t) => `<Tag><Key>${t.Key}</Key><Value>${t.Value}</Value></Tag>`).join("");
963
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
964
+ <Tagging>
965
+ <TagSet>${tagsXml}</TagSet>
966
+ </Tagging>`;
967
+ await this.client.request({
968
+ service: "s3",
969
+ region: this.region,
970
+ method: "PUT",
971
+ path: `/${bucket}`,
972
+ queryParams: { tagging: "" },
973
+ headers: { "Content-Type": "application/xml" },
974
+ body
975
+ });
976
+ }
977
+ async deleteBucketTagging(bucket) {
978
+ await this.client.request({
979
+ service: "s3",
980
+ region: this.region,
981
+ method: "DELETE",
982
+ path: `/${bucket}`,
983
+ queryParams: { tagging: "" }
984
+ });
985
+ }
986
+ async getObjectTagging(bucket, key) {
987
+ try {
988
+ const result = await this.client.request({
989
+ service: "s3",
990
+ region: this.region,
991
+ method: "GET",
992
+ path: `/${bucket}/${key}`,
993
+ queryParams: { tagging: "" }
994
+ });
995
+ const tagSet = result?.Tagging?.TagSet?.Tag;
996
+ if (!tagSet)
997
+ return [];
998
+ return Array.isArray(tagSet) ? tagSet : [tagSet];
999
+ } catch (e) {
1000
+ if (e.statusCode === 404) {
1001
+ return [];
1002
+ }
1003
+ throw e;
1004
+ }
1005
+ }
1006
+ async putObjectTagging(bucket, key, tags) {
1007
+ const tagsXml = tags.map((t) => `<Tag><Key>${t.Key}</Key><Value>${t.Value}</Value></Tag>`).join("");
1008
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1009
+ <Tagging>
1010
+ <TagSet>${tagsXml}</TagSet>
1011
+ </Tagging>`;
1012
+ await this.client.request({
1013
+ service: "s3",
1014
+ region: this.region,
1015
+ method: "PUT",
1016
+ path: `/${bucket}/${key}`,
1017
+ queryParams: { tagging: "" },
1018
+ headers: { "Content-Type": "application/xml" },
1019
+ body
1020
+ });
1021
+ }
1022
+ async deleteObjectTagging(bucket, key) {
1023
+ await this.client.request({
1024
+ service: "s3",
1025
+ region: this.region,
1026
+ method: "DELETE",
1027
+ path: `/${bucket}/${key}`,
1028
+ queryParams: { tagging: "" }
1029
+ });
1030
+ }
1031
+ async getBucketAcl(bucket) {
1032
+ const result = await this.client.request({
1033
+ service: "s3",
1034
+ region: this.region,
1035
+ method: "GET",
1036
+ path: `/${bucket}`,
1037
+ queryParams: { acl: "" }
1038
+ });
1039
+ return result?.AccessControlPolicy;
1040
+ }
1041
+ async putBucketAcl(bucket, acl) {
1042
+ await this.client.request({
1043
+ service: "s3",
1044
+ region: this.region,
1045
+ method: "PUT",
1046
+ path: `/${bucket}`,
1047
+ queryParams: { acl: "" },
1048
+ headers: { "x-amz-acl": acl }
1049
+ });
1050
+ }
1051
+ async getObjectAcl(bucket, key) {
1052
+ const result = await this.client.request({
1053
+ service: "s3",
1054
+ region: this.region,
1055
+ method: "GET",
1056
+ path: `/${bucket}/${key}`,
1057
+ queryParams: { acl: "" }
1058
+ });
1059
+ return result?.AccessControlPolicy;
1060
+ }
1061
+ async putObjectAcl(bucket, key, acl) {
1062
+ await this.client.request({
1063
+ service: "s3",
1064
+ region: this.region,
1065
+ method: "PUT",
1066
+ path: `/${bucket}/${key}`,
1067
+ queryParams: { acl: "" },
1068
+ headers: { "x-amz-acl": acl }
1069
+ });
1070
+ }
1071
+ async getBucketLocation(bucket) {
1072
+ const result = await this.client.request({
1073
+ service: "s3",
1074
+ region: this.region,
1075
+ method: "GET",
1076
+ path: `/${bucket}`,
1077
+ queryParams: { location: "" }
1078
+ });
1079
+ return result?.LocationConstraint || "us-east-1";
1080
+ }
1081
+ async getBucketLogging(bucket) {
1082
+ const result = await this.client.request({
1083
+ service: "s3",
1084
+ region: this.region,
1085
+ method: "GET",
1086
+ path: `/${bucket}`,
1087
+ queryParams: { logging: "" }
1088
+ });
1089
+ return result?.BucketLoggingStatus;
1090
+ }
1091
+ async putBucketLogging(bucket, targetBucket, targetPrefix) {
1092
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1093
+ <BucketLoggingStatus xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1094
+ <LoggingEnabled>
1095
+ <TargetBucket>${targetBucket}</TargetBucket>
1096
+ <TargetPrefix>${targetPrefix}</TargetPrefix>
1097
+ </LoggingEnabled>
1098
+ </BucketLoggingStatus>`;
1099
+ await this.client.request({
1100
+ service: "s3",
1101
+ region: this.region,
1102
+ method: "PUT",
1103
+ path: `/${bucket}`,
1104
+ queryParams: { logging: "" },
1105
+ headers: { "Content-Type": "application/xml" },
1106
+ body
1107
+ });
1108
+ }
1109
+ async getBucketNotificationConfiguration(bucket) {
1110
+ const result = await this.client.request({
1111
+ service: "s3",
1112
+ region: this.region,
1113
+ method: "GET",
1114
+ path: `/${bucket}`,
1115
+ queryParams: { notification: "" }
1116
+ });
1117
+ return result?.NotificationConfiguration;
1118
+ }
1119
+ async putBucketNotificationConfiguration(bucket, config) {
1120
+ let configXml = "";
1121
+ if (config.LambdaFunctionConfigurations) {
1122
+ for (const c of config.LambdaFunctionConfigurations) {
1123
+ configXml += "<CloudFunctionConfiguration>";
1124
+ if (c.Id)
1125
+ configXml += `<Id>${c.Id}</Id>`;
1126
+ configXml += `<CloudFunction>${c.LambdaFunctionArn}</CloudFunction>`;
1127
+ for (const event of c.Events) {
1128
+ configXml += `<Event>${event}</Event>`;
1129
+ }
1130
+ if (c.Filter?.Key?.FilterRules) {
1131
+ configXml += "<Filter><S3Key>";
1132
+ for (const rule of c.Filter.Key.FilterRules) {
1133
+ configXml += `<FilterRule><Name>${rule.Name}</Name><Value>${rule.Value}</Value></FilterRule>`;
1134
+ }
1135
+ configXml += "</S3Key></Filter>";
1136
+ }
1137
+ configXml += "</CloudFunctionConfiguration>";
1138
+ }
1139
+ }
1140
+ if (config.TopicConfigurations) {
1141
+ for (const c of config.TopicConfigurations) {
1142
+ configXml += "<TopicConfiguration>";
1143
+ if (c.Id)
1144
+ configXml += `<Id>${c.Id}</Id>`;
1145
+ configXml += `<Topic>${c.TopicArn}</Topic>`;
1146
+ for (const event of c.Events) {
1147
+ configXml += `<Event>${event}</Event>`;
1148
+ }
1149
+ configXml += "</TopicConfiguration>";
1150
+ }
1151
+ }
1152
+ if (config.QueueConfigurations) {
1153
+ for (const c of config.QueueConfigurations) {
1154
+ configXml += "<QueueConfiguration>";
1155
+ if (c.Id)
1156
+ configXml += `<Id>${c.Id}</Id>`;
1157
+ configXml += `<Queue>${c.QueueArn}</Queue>`;
1158
+ for (const event of c.Events) {
1159
+ configXml += `<Event>${event}</Event>`;
1160
+ }
1161
+ configXml += "</QueueConfiguration>";
1162
+ }
1163
+ }
1164
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1165
+ <NotificationConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1166
+ ${configXml}
1167
+ </NotificationConfiguration>`;
1168
+ await this.client.request({
1169
+ service: "s3",
1170
+ region: this.region,
1171
+ method: "PUT",
1172
+ path: `/${bucket}`,
1173
+ queryParams: { notification: "" },
1174
+ headers: { "Content-Type": "application/xml" },
1175
+ body
1176
+ });
1177
+ }
1178
+ async getBucketWebsite(bucket) {
1179
+ try {
1180
+ const result = await this.client.request({
1181
+ service: "s3",
1182
+ region: this.region,
1183
+ method: "GET",
1184
+ path: `/${bucket}`,
1185
+ queryParams: { website: "" }
1186
+ });
1187
+ return result?.WebsiteConfiguration;
1188
+ } catch (e) {
1189
+ if (e.statusCode === 404) {
1190
+ return null;
1191
+ }
1192
+ throw e;
1193
+ }
1194
+ }
1195
+ async putBucketWebsite(bucket, config) {
1196
+ let configXml = "";
1197
+ if (config.RedirectAllRequestsTo) {
1198
+ configXml = `<RedirectAllRequestsTo>
1199
+ <HostName>${config.RedirectAllRequestsTo.HostName}</HostName>
1200
+ ${config.RedirectAllRequestsTo.Protocol ? `<Protocol>${config.RedirectAllRequestsTo.Protocol}</Protocol>` : ""}
1201
+ </RedirectAllRequestsTo>`;
1202
+ } else {
1203
+ configXml = `<IndexDocument><Suffix>${config.IndexDocument}</Suffix></IndexDocument>`;
1204
+ if (config.ErrorDocument) {
1205
+ configXml += `<ErrorDocument><Key>${config.ErrorDocument}</Key></ErrorDocument>`;
1206
+ }
1207
+ }
1208
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1209
+ <WebsiteConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1210
+ ${configXml}
1211
+ </WebsiteConfiguration>`;
1212
+ await this.client.request({
1213
+ service: "s3",
1214
+ region: this.region,
1215
+ method: "PUT",
1216
+ path: `/${bucket}`,
1217
+ queryParams: { website: "" },
1218
+ headers: { "Content-Type": "application/xml" },
1219
+ body
1220
+ });
1221
+ }
1222
+ async deleteBucketWebsite(bucket) {
1223
+ await this.client.request({
1224
+ service: "s3",
1225
+ region: this.region,
1226
+ method: "DELETE",
1227
+ path: `/${bucket}`,
1228
+ queryParams: { website: "" }
1229
+ });
1230
+ }
1231
+ async getBucketReplication(bucket) {
1232
+ try {
1233
+ const result = await this.client.request({
1234
+ service: "s3",
1235
+ region: this.region,
1236
+ method: "GET",
1237
+ path: `/${bucket}`,
1238
+ queryParams: { replication: "" }
1239
+ });
1240
+ return result?.ReplicationConfiguration;
1241
+ } catch (e) {
1242
+ if (e.statusCode === 404) {
1243
+ return null;
1244
+ }
1245
+ throw e;
1246
+ }
1247
+ }
1248
+ async deleteBucketReplication(bucket) {
1249
+ await this.client.request({
1250
+ service: "s3",
1251
+ region: this.region,
1252
+ method: "DELETE",
1253
+ path: `/${bucket}`,
1254
+ queryParams: { replication: "" }
1255
+ });
1256
+ }
1257
+ async getPublicAccessBlock(bucket) {
1258
+ try {
1259
+ const result = await this.client.request({
1260
+ service: "s3",
1261
+ region: this.region,
1262
+ method: "GET",
1263
+ path: `/${bucket}`,
1264
+ queryParams: { publicAccessBlock: "" }
1265
+ });
1266
+ return result?.PublicAccessBlockConfiguration;
1267
+ } catch (e) {
1268
+ if (e.statusCode === 404) {
1269
+ return null;
1270
+ }
1271
+ throw e;
1272
+ }
1273
+ }
1274
+ async putPublicAccessBlock(bucket, config) {
1275
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1276
+ <PublicAccessBlockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
1277
+ <BlockPublicAcls>${config.BlockPublicAcls ?? true}</BlockPublicAcls>
1278
+ <IgnorePublicAcls>${config.IgnorePublicAcls ?? true}</IgnorePublicAcls>
1279
+ <BlockPublicPolicy>${config.BlockPublicPolicy ?? true}</BlockPublicPolicy>
1280
+ <RestrictPublicBuckets>${config.RestrictPublicBuckets ?? true}</RestrictPublicBuckets>
1281
+ </PublicAccessBlockConfiguration>`;
1282
+ await this.client.request({
1283
+ service: "s3",
1284
+ region: this.region,
1285
+ method: "PUT",
1286
+ path: `/${bucket}`,
1287
+ queryParams: { publicAccessBlock: "" },
1288
+ headers: { "Content-Type": "application/xml" },
1289
+ body
1290
+ });
1291
+ }
1292
+ async deletePublicAccessBlock(bucket) {
1293
+ await this.client.request({
1294
+ service: "s3",
1295
+ region: this.region,
1296
+ method: "DELETE",
1297
+ path: `/${bucket}`,
1298
+ queryParams: { publicAccessBlock: "" }
1299
+ });
1300
+ }
1301
+ generatePresignedGetUrl(bucket, key, expiresInSeconds = 3600) {
1302
+ const { accessKeyId, secretAccessKey } = this.getCredentials();
1303
+ const host = this.s3VirtualHost(bucket);
1304
+ const now = new Date;
1305
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
1306
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
1307
+ const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
1308
+ const credential = `${accessKeyId}/${credentialScope}`;
1309
+ const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
1310
+ const queryParams = new URLSearchParams({
1311
+ "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
1312
+ "X-Amz-Credential": credential,
1313
+ "X-Amz-Date": amzDate,
1314
+ "X-Amz-Expires": expiresInSeconds.toString(),
1315
+ "X-Amz-SignedHeaders": "host"
1316
+ });
1317
+ const canonicalRequest = [
1318
+ "GET",
1319
+ `/${encodedKey}`,
1320
+ queryParams.toString(),
1321
+ `host:${host}
1322
+ `,
1323
+ "host",
1324
+ "UNSIGNED-PAYLOAD"
1325
+ ].join(`
1326
+ `);
1327
+ const stringToSign = [
1328
+ "AWS4-HMAC-SHA256",
1329
+ amzDate,
1330
+ credentialScope,
1331
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex")
1332
+ ].join(`
1333
+ `);
1334
+ const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
1335
+ const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
1336
+ const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
1337
+ const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
1338
+ const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
1339
+ queryParams.append("X-Amz-Signature", signature);
1340
+ return `https://${host}/${encodedKey}?${queryParams.toString()}`;
1341
+ }
1342
+ generatePresignedPutUrl(bucket, key, contentType, expiresInSeconds = 3600) {
1343
+ const { accessKeyId, secretAccessKey } = this.getCredentials();
1344
+ const host = this.s3VirtualHost(bucket);
1345
+ const now = new Date;
1346
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
1347
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
1348
+ const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
1349
+ const credential = `${accessKeyId}/${credentialScope}`;
1350
+ const encodedKey = key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
1351
+ const queryParams = new URLSearchParams({
1352
+ "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
1353
+ "X-Amz-Credential": credential,
1354
+ "X-Amz-Date": amzDate,
1355
+ "X-Amz-Expires": expiresInSeconds.toString(),
1356
+ "X-Amz-SignedHeaders": "content-type;host"
1357
+ });
1358
+ const canonicalRequest = [
1359
+ "PUT",
1360
+ `/${encodedKey}`,
1361
+ queryParams.toString(),
1362
+ `content-type:${contentType}
1363
+ host:${host}
1364
+ `,
1365
+ "content-type;host",
1366
+ "UNSIGNED-PAYLOAD"
1367
+ ].join(`
1368
+ `);
1369
+ const stringToSign = [
1370
+ "AWS4-HMAC-SHA256",
1371
+ amzDate,
1372
+ credentialScope,
1373
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex")
1374
+ ].join(`
1375
+ `);
1376
+ const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
1377
+ const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
1378
+ const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
1379
+ const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
1380
+ const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
1381
+ queryParams.append("X-Amz-Signature", signature);
1382
+ return `https://${host}/${encodedKey}?${queryParams.toString()}`;
1383
+ }
1384
+ async createMultipartUpload(bucket, key, options) {
1385
+ const headers = {};
1386
+ if (options?.contentType) {
1387
+ headers["Content-Type"] = options.contentType;
1388
+ }
1389
+ if (options?.metadata) {
1390
+ for (const [k, v] of Object.entries(options.metadata)) {
1391
+ headers[`x-amz-meta-${k}`] = v;
1392
+ }
1393
+ }
1394
+ const result = await this.client.request({
1395
+ service: "s3",
1396
+ region: this.region,
1397
+ method: "POST",
1398
+ path: `/${bucket}/${key}`,
1399
+ queryParams: { uploads: "" },
1400
+ headers
1401
+ });
1402
+ return { UploadId: result?.InitiateMultipartUploadResult?.UploadId };
1403
+ }
1404
+ async uploadPart(bucket, key, uploadId, partNumber, body) {
1405
+ const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
1406
+ const host = this.s3VirtualHost(bucket);
1407
+ const url = `https://${host}/${key}?partNumber=${partNumber}&uploadId=${encodeURIComponent(uploadId)}`;
1408
+ const now = new Date;
1409
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
1410
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
1411
+ const payloadHash = crypto.createHash("sha256").update(body).digest("hex");
1412
+ const requestHeaders = {
1413
+ host,
1414
+ "x-amz-date": amzDate,
1415
+ "x-amz-content-sha256": payloadHash
1416
+ };
1417
+ if (sessionToken) {
1418
+ requestHeaders["x-amz-security-token"] = sessionToken;
1419
+ }
1420
+ const canonicalHeaders = Object.keys(requestHeaders).sort().map((k) => `${k.toLowerCase()}:${requestHeaders[k].trim()}
1421
+ `).join("");
1422
+ const signedHeaders = Object.keys(requestHeaders).sort().map((k) => k.toLowerCase()).join(";");
1423
+ const canonicalRequest = [
1424
+ "PUT",
1425
+ `/${key}`,
1426
+ `partNumber=${partNumber}&uploadId=${encodeURIComponent(uploadId)}`,
1427
+ canonicalHeaders,
1428
+ signedHeaders,
1429
+ payloadHash
1430
+ ].join(`
1431
+ `);
1432
+ const algorithm = "AWS4-HMAC-SHA256";
1433
+ const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
1434
+ const stringToSign = [
1435
+ algorithm,
1436
+ amzDate,
1437
+ credentialScope,
1438
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex")
1439
+ ].join(`
1440
+ `);
1441
+ const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
1442
+ const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
1443
+ const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
1444
+ const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
1445
+ const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
1446
+ const authHeader = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
1447
+ const response = await fetch(url, {
1448
+ method: "PUT",
1449
+ headers: {
1450
+ ...requestHeaders,
1451
+ Authorization: authHeader
1452
+ },
1453
+ body: toFetchBody(body)
1454
+ });
1455
+ if (!response.ok) {
1456
+ const text = await response.text();
1457
+ throw new Error(`Upload part failed: ${response.status} ${text}`);
1458
+ }
1459
+ return { ETag: response.headers.get("etag") || "" };
1460
+ }
1461
+ async completeMultipartUpload(bucket, key, uploadId, parts) {
1462
+ const partsXml = parts.sort((a, b) => a.PartNumber - b.PartNumber).map((p) => `<Part><PartNumber>${p.PartNumber}</PartNumber><ETag>${p.ETag}</ETag></Part>`).join("");
1463
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1464
+ <CompleteMultipartUpload>${partsXml}</CompleteMultipartUpload>`;
1465
+ await this.client.request({
1466
+ service: "s3",
1467
+ region: this.region,
1468
+ method: "POST",
1469
+ path: `/${bucket}/${key}`,
1470
+ queryParams: { uploadId },
1471
+ headers: { "Content-Type": "application/xml" },
1472
+ body
1473
+ });
1474
+ }
1475
+ async abortMultipartUpload(bucket, key, uploadId) {
1476
+ await this.client.request({
1477
+ service: "s3",
1478
+ region: this.region,
1479
+ method: "DELETE",
1480
+ path: `/${bucket}/${key}`,
1481
+ queryParams: { uploadId }
1482
+ });
1483
+ }
1484
+ async listMultipartUploads(bucket) {
1485
+ const result = await this.client.request({
1486
+ service: "s3",
1487
+ region: this.region,
1488
+ method: "GET",
1489
+ path: `/${bucket}`,
1490
+ queryParams: { uploads: "" }
1491
+ });
1492
+ const uploads = result?.ListMultipartUploadsResult?.Upload;
1493
+ if (!uploads)
1494
+ return [];
1495
+ const list = Array.isArray(uploads) ? uploads : [uploads];
1496
+ return list.map((u) => ({
1497
+ Key: u.Key,
1498
+ UploadId: u.UploadId,
1499
+ Initiated: u.Initiated
1500
+ }));
1501
+ }
1502
+ async restoreObject(bucket, key, days, tier = "Standard") {
1503
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1504
+ <RestoreRequest>
1505
+ <Days>${days}</Days>
1506
+ <GlacierJobParameters>
1507
+ <Tier>${tier}</Tier>
1508
+ </GlacierJobParameters>
1509
+ </RestoreRequest>`;
1510
+ await this.client.request({
1511
+ service: "s3",
1512
+ region: this.region,
1513
+ method: "POST",
1514
+ path: `/${bucket}/${key}`,
1515
+ queryParams: { restore: "" },
1516
+ headers: { "Content-Type": "application/xml" },
1517
+ body
1518
+ });
1519
+ }
1520
+ async selectObjectContent(bucket, key, expression, inputFormat, outputFormat = "JSON") {
1521
+ let inputSerialization = "";
1522
+ if (inputFormat === "CSV") {
1523
+ inputSerialization = "<CSV><FileHeaderInfo>USE</FileHeaderInfo></CSV>";
1524
+ } else if (inputFormat === "JSON") {
1525
+ inputSerialization = "<JSON><Type>DOCUMENT</Type></JSON>";
1526
+ } else {
1527
+ inputSerialization = "<Parquet/>";
1528
+ }
1529
+ let outputSerialization = "";
1530
+ if (outputFormat === "CSV") {
1531
+ outputSerialization = "<CSV/>";
1532
+ } else {
1533
+ outputSerialization = "<JSON/>";
1534
+ }
1535
+ const body = `<?xml version="1.0" encoding="UTF-8"?>
1536
+ <SelectObjectContentRequest>
1537
+ <Expression>${expression}</Expression>
1538
+ <ExpressionType>SQL</ExpressionType>
1539
+ <InputSerialization>${inputSerialization}</InputSerialization>
1540
+ <OutputSerialization>${outputSerialization}</OutputSerialization>
1541
+ </SelectObjectContentRequest>`;
1542
+ const result = await this.client.request({
1543
+ service: "s3",
1544
+ region: this.region,
1545
+ method: "POST",
1546
+ path: `/${bucket}/${key}`,
1547
+ queryParams: { select: "", "select-type": "2" },
1548
+ headers: { "Content-Type": "application/xml" },
1549
+ body,
1550
+ rawResponse: true
1551
+ });
1552
+ return result;
1553
+ }
1554
+ async getSignedUrl(options) {
1555
+ const { bucket, key, expiresIn = 3600, operation = "getObject" } = options;
1556
+ const { accessKeyId, secretAccessKey, sessionToken } = this.getCredentials();
1557
+ const now = new Date;
1558
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
1559
+ const dateStamp = now.toISOString().slice(0, 10).replace(/-/g, "");
1560
+ const host = this.s3VirtualHost(bucket);
1561
+ const method = operation === "putObject" ? "PUT" : "GET";
1562
+ const algorithm = "AWS4-HMAC-SHA256";
1563
+ const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;
1564
+ const credential = `${accessKeyId}/${credentialScope}`;
1565
+ const queryParams = {
1566
+ "X-Amz-Algorithm": algorithm,
1567
+ "X-Amz-Credential": credential,
1568
+ "X-Amz-Date": amzDate,
1569
+ "X-Amz-Expires": expiresIn.toString(),
1570
+ "X-Amz-SignedHeaders": "host"
1571
+ };
1572
+ if (sessionToken) {
1573
+ queryParams["X-Amz-Security-Token"] = sessionToken;
1574
+ }
1575
+ const sortedParams = Object.keys(queryParams).sort();
1576
+ const canonicalQuerystring = sortedParams.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(queryParams[k])}`).join("&");
1577
+ const canonicalUri = "/" + key.split("/").map((seg) => encodeURIComponent(seg)).join("/");
1578
+ const canonicalHeaders = `host:${host}
1579
+ `;
1580
+ const signedHeaders = "host";
1581
+ const payloadHash = "UNSIGNED-PAYLOAD";
1582
+ const canonicalRequest = [
1583
+ method,
1584
+ canonicalUri,
1585
+ canonicalQuerystring,
1586
+ canonicalHeaders,
1587
+ signedHeaders,
1588
+ payloadHash
1589
+ ].join(`
1590
+ `);
1591
+ const stringToSign = [
1592
+ algorithm,
1593
+ amzDate,
1594
+ credentialScope,
1595
+ crypto.createHash("sha256").update(canonicalRequest).digest("hex")
1596
+ ].join(`
1597
+ `);
1598
+ const kDate = crypto.createHmac("sha256", `AWS4${secretAccessKey}`).update(dateStamp).digest();
1599
+ const kRegion = crypto.createHmac("sha256", kDate).update(this.region).digest();
1600
+ const kService = crypto.createHmac("sha256", kRegion).update("s3").digest();
1601
+ const kSigning = crypto.createHmac("sha256", kService).update("aws4_request").digest();
1602
+ const signature = crypto.createHmac("sha256", kSigning).update(stringToSign).digest("hex");
1603
+ const presignedUrl = `https://${host}${canonicalUri}?${canonicalQuerystring}&X-Amz-Signature=${signature}`;
1604
+ return presignedUrl;
1605
+ }
1606
+ async listObjects(options) {
1607
+ const { bucket, prefix, maxKeys = 1000, continuationToken } = options;
1608
+ const queryParams = {
1609
+ "list-type": "2",
1610
+ "max-keys": maxKeys.toString()
1611
+ };
1612
+ if (prefix)
1613
+ queryParams.prefix = prefix;
1614
+ if (continuationToken)
1615
+ queryParams["continuation-token"] = continuationToken;
1616
+ const result = await this.client.request({
1617
+ service: "s3",
1618
+ region: this.region,
1619
+ method: "GET",
1620
+ path: `/${bucket}`,
1621
+ queryParams
1622
+ });
1623
+ const objects = [];
1624
+ const listResult = result?.ListBucketResult ?? result;
1625
+ if (listResult?.Contents) {
1626
+ const items = Array.isArray(listResult.Contents) ? listResult.Contents : [listResult.Contents];
1627
+ for (const item of items) {
1628
+ objects.push({
1629
+ Key: item.Key || "",
1630
+ LastModified: item.LastModified || "",
1631
+ Size: Number.parseInt(item.Size || "0"),
1632
+ ETag: item.ETag
1633
+ });
1634
+ }
1635
+ }
1636
+ return {
1637
+ objects,
1638
+ nextContinuationToken: listResult?.NextContinuationToken
1639
+ };
1640
+ }
1641
+ async emptyBucket(bucket) {
1642
+ let deletedCount = 0;
1643
+ const objects = await this.listAllObjects({ bucket });
1644
+ if (objects.length > 0) {
1645
+ for (let i = 0;i < objects.length; i += 1000) {
1646
+ const batch = objects.slice(i, i + 1000);
1647
+ const keys = batch.map((obj) => obj.Key);
1648
+ await this.deleteObjects(bucket, keys);
1649
+ deletedCount += keys.length;
1650
+ }
1651
+ }
1652
+ try {
1653
+ let keyMarker;
1654
+ let versionIdMarker;
1655
+ do {
1656
+ const versionsResult = await this.listObjectVersions({
1657
+ bucket,
1658
+ keyMarker,
1659
+ versionIdMarker,
1660
+ maxKeys: 1000
1661
+ });
1662
+ const versionsToDelete = [];
1663
+ if (versionsResult.versions) {
1664
+ for (const version of versionsResult.versions) {
1665
+ versionsToDelete.push({ Key: version.Key, VersionId: version.VersionId });
1666
+ }
1667
+ }
1668
+ if (versionsResult.deleteMarkers) {
1669
+ for (const marker of versionsResult.deleteMarkers) {
1670
+ versionsToDelete.push({ Key: marker.Key, VersionId: marker.VersionId });
1671
+ }
1672
+ }
1673
+ if (versionsToDelete.length > 0) {
1674
+ await this.deleteObjectVersions(bucket, versionsToDelete);
1675
+ deletedCount += versionsToDelete.length;
1676
+ }
1677
+ keyMarker = versionsResult.nextKeyMarker;
1678
+ versionIdMarker = versionsResult.nextVersionIdMarker;
1679
+ } while (keyMarker);
1680
+ } catch {}
1681
+ return { deletedCount };
1682
+ }
1683
+ async listObjectVersions(options) {
1684
+ const { bucket, prefix, keyMarker, versionIdMarker, maxKeys = 1000 } = options;
1685
+ const queryParams = {
1686
+ versions: "",
1687
+ "max-keys": maxKeys.toString()
1688
+ };
1689
+ if (prefix)
1690
+ queryParams.prefix = prefix;
1691
+ if (keyMarker)
1692
+ queryParams["key-marker"] = keyMarker;
1693
+ if (versionIdMarker)
1694
+ queryParams["version-id-marker"] = versionIdMarker;
1695
+ const result = await this.client.request({
1696
+ service: "s3",
1697
+ region: this.region,
1698
+ method: "GET",
1699
+ path: `/${bucket}`,
1700
+ queryParams
1701
+ });
1702
+ const versions = [];
1703
+ const deleteMarkers = [];
1704
+ if (result.Version) {
1705
+ const versionList = Array.isArray(result.Version) ? result.Version : [result.Version];
1706
+ for (const v of versionList) {
1707
+ versions.push({
1708
+ Key: v.Key,
1709
+ VersionId: v.VersionId,
1710
+ IsLatest: v.IsLatest === "true"
1711
+ });
1712
+ }
1713
+ }
1714
+ if (result.DeleteMarker) {
1715
+ const markerList = Array.isArray(result.DeleteMarker) ? result.DeleteMarker : [result.DeleteMarker];
1716
+ for (const m of markerList) {
1717
+ deleteMarkers.push({
1718
+ Key: m.Key,
1719
+ VersionId: m.VersionId,
1720
+ IsLatest: m.IsLatest === "true"
1721
+ });
1722
+ }
1723
+ }
1724
+ return {
1725
+ versions,
1726
+ deleteMarkers,
1727
+ nextKeyMarker: result.NextKeyMarker,
1728
+ nextVersionIdMarker: result.NextVersionIdMarker
1729
+ };
1730
+ }
1731
+ async deleteObjectVersions(bucket, objects) {
1732
+ const deleteXml = `<?xml version="1.0" encoding="UTF-8"?>
1733
+ <Delete>
1734
+ <Quiet>true</Quiet>
1735
+ ${objects.map((obj) => `<Object><Key>${obj.Key}</Key>${obj.VersionId ? `<VersionId>${obj.VersionId}</VersionId>` : ""}</Object>`).join(`
1736
+ `)}
1737
+ </Delete>`;
1738
+ const contentMd5 = crypto.createHash("md5").update(deleteXml).digest("base64");
1739
+ await this.client.request({
1740
+ service: "s3",
1741
+ region: this.region,
1742
+ method: "POST",
1743
+ path: `/${bucket}`,
1744
+ queryParams: { delete: "" },
1745
+ body: deleteXml,
1746
+ headers: {
1747
+ "Content-Type": "application/xml",
1748
+ "Content-MD5": contentMd5
1749
+ }
1750
+ });
1751
+ }
1752
+ }
1753
+
1754
+ export { resolveCredentials, S3Client };