@stacksjs/ts-cloud 0.9.2 → 0.9.4

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