@stacksjs/ts-cloud 0.9.3 → 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 (143) hide show
  1. package/dist/aws/index.js +1 -269
  2. package/dist/bin/chunk-00kg80qg.js +1 -0
  3. package/dist/bin/{chunk-hzqw8xm5.js → chunk-1k5kszjp.js} +1 -1
  4. package/dist/bin/{chunk-9v39615t.js → chunk-21e0mavm.js} +1 -1
  5. package/dist/bin/{chunk-waayf9ay.js → chunk-2empps47.js} +1 -1
  6. package/dist/bin/{chunk-14cwr6eh.js → chunk-3vqfexc4.js} +1 -1
  7. package/dist/bin/chunk-5ectjq0b.js +1 -0
  8. package/dist/bin/{chunk-b5gd39s8.js → chunk-5erkxpt7.js} +4 -4
  9. package/dist/bin/{chunk-41x17hw1.js → chunk-5mrtzemj.js} +1 -1
  10. package/dist/bin/{chunk-p8q9bq9g.js → chunk-5n9fmabb.js} +1 -1
  11. package/dist/bin/{chunk-gqf6jsmy.js → chunk-9knpg30y.js} +1 -1
  12. package/dist/bin/{chunk-r3kv0vm9.js → chunk-ddk4rt4j.js} +1 -1
  13. package/dist/bin/{chunk-avm7tnwe.js → chunk-e0rkrq70.js} +1 -1
  14. package/dist/bin/{chunk-ry030a6b.js → chunk-etrb03mt.js} +1 -1
  15. package/dist/bin/{chunk-8v7ddyvf.js → chunk-k9t9wvms.js} +1 -1
  16. package/dist/bin/{chunk-92mea34a.js → chunk-m7dgskt1.js} +1 -1
  17. package/dist/bin/{chunk-3r4fb59r.js → chunk-mz0wbbsj.js} +1 -1
  18. package/dist/bin/{chunk-b9qbp8j1.js → chunk-qk9qgepa.js} +1 -1
  19. package/dist/bin/chunk-rxcvf10f.js +7364 -0
  20. package/dist/bin/{chunk-r9hjwcj9.js → chunk-tga3hhwk.js} +1 -1
  21. package/dist/bin/{chunk-ynjvbphf.js → chunk-tx0tqq2k.js} +1 -1
  22. package/dist/bin/{chunk-57y1zxbf.js → chunk-vm3q5gf2.js} +1 -1
  23. package/dist/bin/{chunk-bz828v8q.js → chunk-y7s5r8y6.js} +1 -1
  24. package/dist/bin/{chunk-sgyffpgm.js → chunk-zmegvjp8.js} +1 -1
  25. package/dist/bin/cli.js +14 -14
  26. package/dist/bin/dashboard-server.js +1 -1
  27. package/dist/chunk-0ccta6z6.js +6 -0
  28. package/dist/chunk-0k1cs6pp.js +1 -0
  29. package/dist/chunk-1egjn203.js +704 -0
  30. package/dist/chunk-2wx29qga.js +5 -0
  31. package/dist/chunk-5m5rqmv7.js +1 -0
  32. package/dist/chunk-8ea5022g.js +114 -0
  33. package/dist/chunk-8xqn3c0w.js +2 -0
  34. package/dist/chunk-b4esxhmw.js +2 -0
  35. package/dist/chunk-b8e5wzwn.js +44 -0
  36. package/dist/chunk-bggt4n6e.js +2 -0
  37. package/dist/chunk-cwkg37rs.js +37 -0
  38. package/dist/chunk-dskvqt67.js +7355 -0
  39. package/dist/chunk-gahsdmxf.js +1617 -0
  40. package/dist/chunk-gnv3r2jg.js +1 -0
  41. package/dist/chunk-hg3c48cp.js +34 -0
  42. package/dist/chunk-m5azeva3.js +2 -0
  43. package/dist/chunk-r3qf56b3.js +92 -0
  44. package/dist/chunk-sdqa9ghv.js +141 -0
  45. package/dist/chunk-svrvvcm7.js +1 -0
  46. package/dist/chunk-v0p3hpc3.js +60 -0
  47. package/dist/chunk-wdzjbecg.js +50 -0
  48. package/dist/chunk-ww8k3g7j.js +1 -0
  49. package/dist/chunk-xmqfc62w.js +1 -0
  50. package/dist/chunk-xrebj679.js +435 -0
  51. package/dist/chunk-xt3s24vr.js +2 -0
  52. package/dist/chunk-y21zk0pn.js +4 -0
  53. package/dist/deploy/index.js +1 -168
  54. package/dist/dns/index.js +1 -41
  55. package/dist/drivers/index.js +1 -154
  56. package/dist/index.js +100 -6129
  57. package/dist/protection/index.js +1 -89
  58. package/dist/push/index.js +1 -509
  59. package/dist/spend/index.js +1 -173
  60. package/dist/ui/404.html +1 -1
  61. package/dist/ui/accept-invitation.html +1 -1
  62. package/dist/ui/access-denied.html +1 -1
  63. package/dist/ui/account/automation.html +6 -6
  64. package/dist/ui/account/security.html +2 -2
  65. package/dist/ui/applications/compose.html +6 -6
  66. package/dist/ui/applications/new.html +2 -2
  67. package/dist/ui/data/backups.html +5 -5
  68. package/dist/ui/data/services.html +5 -5
  69. package/dist/ui/data/volumes.html +6 -6
  70. package/dist/ui/index.html +6 -6
  71. package/dist/ui/infrastructure/topology.html +5 -5
  72. package/dist/ui/integrations.html +1 -1
  73. package/dist/ui/operations/alerts.html +5 -5
  74. package/dist/ui/operations/configuration.html +5 -5
  75. package/dist/ui/operations/jobs.html +5 -5
  76. package/dist/ui/operations/maintenance.html +5 -5
  77. package/dist/ui/operations/observability.html +6 -6
  78. package/dist/ui/operations/previews.html +6 -6
  79. package/dist/ui/operations/queue.html +6 -6
  80. package/dist/ui/operations/regions.html +6 -6
  81. package/dist/ui/operations/releases.html +6 -6
  82. package/dist/ui/operations/spend.html +6 -6
  83. package/dist/ui/operations/workloads.html +6 -6
  84. package/dist/ui/security.html +1 -1
  85. package/dist/ui/server/actions.html +5 -5
  86. package/dist/ui/server/activity.html +1 -1
  87. package/dist/ui/server/capacity.html +6 -6
  88. package/dist/ui/server/database.html +6 -6
  89. package/dist/ui/server/deployments.html +6 -6
  90. package/dist/ui/server/diagnostics.html +2 -2
  91. package/dist/ui/server/firewall.html +6 -6
  92. package/dist/ui/server/fleet.html +6 -6
  93. package/dist/ui/server/logs.html +6 -6
  94. package/dist/ui/server/metrics.html +6 -6
  95. package/dist/ui/server/security.html +2 -2
  96. package/dist/ui/server/services.html +1 -1
  97. package/dist/ui/server/sites.html +6 -6
  98. package/dist/ui/server/ssh-keys.html +6 -6
  99. package/dist/ui/server/team.html +6 -6
  100. package/dist/ui/server/terminal.html +1 -1
  101. package/dist/ui/serverless/alarms.html +5 -5
  102. package/dist/ui/serverless/assets.html +2 -2
  103. package/dist/ui/serverless/cost.html +1 -1
  104. package/dist/ui/serverless/data.html +6 -6
  105. package/dist/ui/serverless/deployments.html +2 -2
  106. package/dist/ui/serverless/firewall.html +1 -1
  107. package/dist/ui/serverless/functions.html +6 -6
  108. package/dist/ui/serverless/logs.html +6 -6
  109. package/dist/ui/serverless/metrics.html +2 -2
  110. package/dist/ui/serverless/queues.html +6 -6
  111. package/dist/ui/serverless/secrets.html +6 -6
  112. package/dist/ui/serverless/traces.html +6 -6
  113. package/dist/ui/serverless.html +5 -5
  114. package/package.json +3 -3
  115. package/dist/bin/chunk-jpjamjey.js +0 -6935
  116. package/dist/bin/chunk-m2zwtapr.js +0 -1
  117. package/dist/bin/chunk-rtczra13.js +0 -1
  118. package/dist/chunk-01d86gt1.js +0 -1324
  119. package/dist/chunk-050r7ycy.js +0 -46516
  120. package/dist/chunk-1c8dbv57.js +0 -7278
  121. package/dist/chunk-29rse4p1.js +0 -12203
  122. package/dist/chunk-32e7ya18.js +0 -642
  123. package/dist/chunk-4cjrg98a.js +0 -597
  124. package/dist/chunk-50jpda9q.js +0 -371
  125. package/dist/chunk-6a1t7fnq.js +0 -12121
  126. package/dist/chunk-728jvrr0.js +0 -1701
  127. package/dist/chunk-7m60qnc8.js +0 -1762
  128. package/dist/chunk-aa2dkn7b.js +0 -205
  129. package/dist/chunk-d48vtdj4.js +0 -433
  130. package/dist/chunk-f55nkpvb.js +0 -8
  131. package/dist/chunk-fgk8cqew.js +0 -152
  132. package/dist/chunk-he9a874b.js +0 -769
  133. package/dist/chunk-k2yyatfw.js +0 -10
  134. package/dist/chunk-k4px0bnv.js +0 -23
  135. package/dist/chunk-nsgqsmrh.js +0 -1779
  136. package/dist/chunk-q0st3se8.js +0 -42033
  137. package/dist/chunk-qr803fye.js +0 -2122
  138. package/dist/chunk-rds0sy87.js +0 -1771
  139. package/dist/chunk-ttz2ra5j.js +0 -12
  140. package/dist/chunk-v0bahtg2.js +0 -4
  141. package/dist/chunk-va2yd85b.js +0 -200
  142. package/dist/chunk-x9s0cqfx.js +0 -13
  143. package/dist/chunk-zqtpg06c.js +0 -1752
@@ -1,1324 +0,0 @@
1
- import {
2
- AWSClient
3
- } from "./chunk-zqtpg06c.js";
4
-
5
- // src/aws/cloudfront.ts
6
- class CloudFrontClient {
7
- client;
8
- constructor(profile, transport) {
9
- this.client = transport ?? new AWSClient(undefined, { profile });
10
- }
11
- collectionItems(value, child) {
12
- const items = value?.Items ?? value?.[child];
13
- if (!items)
14
- return [];
15
- if (Array.isArray(items))
16
- return items;
17
- const nested = items[child] ?? items.Item;
18
- if (nested === undefined)
19
- return typeof items === "object" && (items.Id || items.PathPattern) ? [items] : [];
20
- return Array.isArray(nested) ? nested : [nested];
21
- }
22
- validateExistingOriginInput(distributionId, input) {
23
- if (!/^[A-Z0-9]{8,32}$/.test(distributionId))
24
- throw new Error("CloudFront distribution ID is invalid");
25
- if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/.test(input.id))
26
- throw new Error("CloudFront origin ID is invalid");
27
- if (!/^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(input.domainName))
28
- throw new Error("CloudFront origin must be a hostname without a scheme or path");
29
- const pathPattern = input.pathPattern.startsWith("/") ? input.pathPattern : `/${input.pathPattern}`;
30
- if (pathPattern === "/" || pathPattern === "/*" || !/^\/[A-Za-z0-9_.*?~!$&'()+,;=:@%/-]{1,255}$/.test(pathPattern))
31
- throw new Error("CloudFront path pattern must be a non-default absolute pattern");
32
- if (input.originPath && (!input.originPath.startsWith("/") || input.originPath.includes("..")))
33
- throw new Error("CloudFront origin path must be absolute and traversal-free");
34
- if (Object.entries(input.customHeaders ?? {}).some(([name, value]) => !/^[A-Za-z0-9-]{1,64}$/.test(name) || !value || value.length > 1024))
35
- throw new Error("CloudFront custom origin headers are invalid");
36
- return { ...input, pathPattern };
37
- }
38
- async upsertExistingDistributionOrigin(distributionId, rawInput) {
39
- const input = this.validateExistingOriginInput(distributionId, rawInput);
40
- const getResult = await this.client.request({
41
- service: "cloudfront",
42
- region: "us-east-1",
43
- method: "GET",
44
- path: `/2020-05-31/distribution/${distributionId}/config`,
45
- returnHeaders: true
46
- });
47
- const etag = getResult.headers?.etag || getResult.headers?.ETag || getResult.ETag || "";
48
- const config = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
49
- if (!config || config.Enabled === undefined || !etag)
50
- throw new Error("Failed to read an ETag-protected CloudFront distribution config");
51
- const origins = this.collectionItems(config.Origins, "Origin");
52
- const behaviors = this.collectionItems(config.CacheBehaviors, "CacheBehavior");
53
- const existingOrigin = origins.find((value) => String(value.Id) === input.id);
54
- const existingBehavior = behaviors.find((value) => String(value.PathPattern) === input.pathPattern);
55
- if (existingOrigin && String(existingOrigin.DomainName) !== input.domainName && !input.replaceExisting)
56
- throw new Error(`Origin ${input.id} already targets ${existingOrigin.DomainName}; pass replaceExisting only after reviewing the live config`);
57
- if (existingBehavior && String(existingBehavior.TargetOriginId) !== input.id && !input.replaceExisting)
58
- throw new Error(`Path ${input.pathPattern} already targets ${existingBehavior.TargetOriginId}; pass replaceExisting only after reviewing the live config`);
59
- const origin = {
60
- Id: input.id,
61
- DomainName: input.domainName,
62
- OriginPath: input.originPath ?? "",
63
- CustomHeaders: {
64
- Quantity: Object.keys(input.customHeaders ?? {}).length,
65
- ...Object.keys(input.customHeaders ?? {}).length ? {
66
- Items: {
67
- OriginCustomHeader: Object.entries(input.customHeaders).map(([HeaderName, HeaderValue]) => ({
68
- HeaderName,
69
- HeaderValue
70
- }))
71
- }
72
- } : {}
73
- },
74
- CustomOriginConfig: {
75
- HTTPPort: 80,
76
- HTTPSPort: 443,
77
- OriginProtocolPolicy: input.protocolPolicy ?? "https-only",
78
- OriginSslProtocols: { Quantity: 1, Items: { SslProtocol: ["TLSv1.2"] } },
79
- OriginReadTimeout: 30,
80
- OriginKeepaliveTimeout: 5
81
- },
82
- ConnectionAttempts: 3,
83
- ConnectionTimeout: 10,
84
- ...input.originAccessControlId ? { OriginAccessControlId: input.originAccessControlId } : {}
85
- };
86
- const methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
87
- const behavior = {
88
- PathPattern: input.pathPattern,
89
- TargetOriginId: input.id,
90
- ViewerProtocolPolicy: "redirect-to-https",
91
- Compress: true,
92
- CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad",
93
- OriginRequestPolicyId: "b689b0a8-53d0-40ab-baf2-68738e2966ac",
94
- AllowedMethods: {
95
- Quantity: methods.length,
96
- Items: { Method: methods },
97
- CachedMethods: { Quantity: 2, Items: { Method: ["GET", "HEAD"] } }
98
- },
99
- SmoothStreaming: false,
100
- FieldLevelEncryptionId: "",
101
- TrustedSigners: { Enabled: false, Quantity: 0 },
102
- TrustedKeyGroups: { Enabled: false, Quantity: 0 },
103
- LambdaFunctionAssociations: { Quantity: 0 },
104
- FunctionAssociations: { Quantity: 0 }
105
- };
106
- const nextOrigins = existingOrigin ? origins.map((value) => String(value.Id) === input.id ? origin : value) : [...origins, origin];
107
- const nextBehaviors = existingBehavior ? behaviors.map((value) => String(value.PathPattern) === input.pathPattern ? behavior : value) : [...behaviors, behavior];
108
- const changed = JSON.stringify(origins) !== JSON.stringify(nextOrigins) || JSON.stringify(behaviors) !== JSON.stringify(nextBehaviors);
109
- if (!changed || input.dryRun)
110
- return {
111
- distributionId,
112
- originId: input.id,
113
- domainName: input.domainName,
114
- pathPattern: input.pathPattern,
115
- changed,
116
- applied: false,
117
- etag
118
- };
119
- config.Origins = { Quantity: nextOrigins.length, Items: { Origin: nextOrigins } };
120
- config.CacheBehaviors = {
121
- Quantity: nextBehaviors.length,
122
- ...nextBehaviors.length ? { Items: { CacheBehavior: nextBehaviors } } : {}
123
- };
124
- const result = await this.client.request({
125
- service: "cloudfront",
126
- region: "us-east-1",
127
- method: "PUT",
128
- path: `/2020-05-31/distribution/${distributionId}/config`,
129
- body: this.buildDistributionConfigXml(config),
130
- headers: { "Content-Type": "application/xml", "If-Match": etag }
131
- });
132
- return {
133
- distributionId,
134
- originId: input.id,
135
- domainName: input.domainName,
136
- pathPattern: input.pathPattern,
137
- changed: true,
138
- applied: true,
139
- etag: result.ETag || result.headers?.etag || ""
140
- };
141
- }
142
- async removeExistingDistributionOrigin(distributionId, rawInput) {
143
- const input = this.validateExistingOriginInput(distributionId, rawInput);
144
- const getResult = await this.client.request({
145
- service: "cloudfront",
146
- region: "us-east-1",
147
- method: "GET",
148
- path: `/2020-05-31/distribution/${distributionId}/config`,
149
- returnHeaders: true
150
- });
151
- const etag = getResult.headers?.etag || getResult.headers?.ETag || getResult.ETag || "";
152
- const config = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
153
- if (!config || config.Enabled === undefined || !etag)
154
- throw new Error("Failed to read an ETag-protected CloudFront distribution config");
155
- const origins = this.collectionItems(config.Origins, "Origin");
156
- const behaviors = this.collectionItems(config.CacheBehaviors, "CacheBehavior");
157
- const origin = origins.find((value) => String(value.Id) === input.id);
158
- const behavior = behaviors.find((value) => String(value.PathPattern) === input.pathPattern);
159
- if (origin && String(origin.DomainName) !== input.domainName)
160
- throw new Error(`Origin ${input.id} no longer targets ${input.domainName}; refusing ambiguous removal`);
161
- if (behavior && String(behavior.TargetOriginId) !== input.id)
162
- throw new Error(`Path ${input.pathPattern} no longer targets ${input.id}; refusing ambiguous removal`);
163
- if (!behavior)
164
- return {
165
- distributionId,
166
- originId: input.id,
167
- domainName: input.domainName,
168
- pathPattern: input.pathPattern,
169
- changed: false,
170
- applied: false,
171
- originRemoved: false,
172
- etag
173
- };
174
- const nextBehaviors = behaviors.filter((value) => String(value.PathPattern) !== input.pathPattern);
175
- const stillReferenced = String(config.DefaultCacheBehavior?.TargetOriginId) === input.id || nextBehaviors.some((value) => String(value.TargetOriginId) === input.id);
176
- const nextOrigins = stillReferenced ? origins : origins.filter((value) => String(value.Id) !== input.id);
177
- if (input.dryRun)
178
- return {
179
- distributionId,
180
- originId: input.id,
181
- domainName: input.domainName,
182
- pathPattern: input.pathPattern,
183
- changed: true,
184
- applied: false,
185
- originRemoved: !stillReferenced && !!origin,
186
- etag
187
- };
188
- config.Origins = { Quantity: nextOrigins.length, Items: { Origin: nextOrigins } };
189
- config.CacheBehaviors = {
190
- Quantity: nextBehaviors.length,
191
- ...nextBehaviors.length ? { Items: { CacheBehavior: nextBehaviors } } : {}
192
- };
193
- const result = await this.client.request({
194
- service: "cloudfront",
195
- region: "us-east-1",
196
- method: "PUT",
197
- path: `/2020-05-31/distribution/${distributionId}/config`,
198
- body: this.buildDistributionConfigXml(config),
199
- headers: { "Content-Type": "application/xml", "If-Match": etag }
200
- });
201
- return {
202
- distributionId,
203
- originId: input.id,
204
- domainName: input.domainName,
205
- pathPattern: input.pathPattern,
206
- changed: true,
207
- applied: true,
208
- originRemoved: !stillReferenced && !!origin,
209
- etag: result.ETag || result.headers?.etag || ""
210
- };
211
- }
212
- async createInvalidation(options) {
213
- const callerReference = options.callerReference || Date.now().toString();
214
- const invalidationBatchXml = `<?xml version="1.0" encoding="UTF-8"?>
215
- <InvalidationBatch>
216
- <Paths>
217
- <Quantity>${options.paths.length}</Quantity>
218
- <Items>
219
- ${options.paths.map((path) => `<Path>${path}</Path>`).join(`
220
- `)}
221
- </Items>
222
- </Paths>
223
- <CallerReference>${callerReference}</CallerReference>
224
- </InvalidationBatch>`;
225
- const result = await this.client.request({
226
- service: "cloudfront",
227
- region: "us-east-1",
228
- method: "POST",
229
- path: `/2020-05-31/distribution/${options.distributionId}/invalidation`,
230
- body: invalidationBatchXml,
231
- headers: {
232
- "Content-Type": "application/xml"
233
- }
234
- });
235
- return {
236
- Id: result.Id || result.Invalidation?.Id,
237
- Status: result.Status || result.Invalidation?.Status || "InProgress",
238
- CreateTime: result.CreateTime || result.Invalidation?.CreateTime || new Date().toISOString()
239
- };
240
- }
241
- async getInvalidation(distributionId, invalidationId) {
242
- const result = await this.client.request({
243
- service: "cloudfront",
244
- region: "us-east-1",
245
- method: "GET",
246
- path: `/2020-05-31/distribution/${distributionId}/invalidation/${invalidationId}`
247
- });
248
- return {
249
- Id: result.Id || result.Invalidation?.Id,
250
- Status: result.Status || result.Invalidation?.Status,
251
- CreateTime: result.CreateTime || result.Invalidation?.CreateTime
252
- };
253
- }
254
- async listInvalidations(distributionId) {
255
- const result = await this.client.request({
256
- service: "cloudfront",
257
- region: "us-east-1",
258
- method: "GET",
259
- path: `/2020-05-31/distribution/${distributionId}/invalidation`
260
- });
261
- const invalidations = [];
262
- if (result.InvalidationSummary) {
263
- const summaries = Array.isArray(result.InvalidationSummary) ? result.InvalidationSummary : [result.InvalidationSummary];
264
- invalidations.push(...summaries.map((item) => ({
265
- Id: item.Id,
266
- Status: item.Status,
267
- CreateTime: item.CreateTime
268
- })));
269
- }
270
- return invalidations;
271
- }
272
- async waitForInvalidation(distributionId, invalidationId) {
273
- const maxAttempts = 60;
274
- let attempts = 0;
275
- while (attempts < maxAttempts) {
276
- const invalidation = await this.getInvalidation(distributionId, invalidationId);
277
- if (invalidation.Status === "Completed") {
278
- return;
279
- }
280
- await new Promise((resolve) => setTimeout(resolve, 5000));
281
- attempts++;
282
- }
283
- throw new Error(`Timeout waiting for invalidation ${invalidationId} to complete`);
284
- }
285
- async listDistributions() {
286
- const result = await this.client.request({
287
- service: "cloudfront",
288
- region: "us-east-1",
289
- method: "GET",
290
- path: "/2020-05-31/distribution"
291
- });
292
- const distributions = [];
293
- const distList = result.DistributionList || result;
294
- const items = distList.Items;
295
- let summaries = [];
296
- if (Array.isArray(items)) {
297
- summaries = items;
298
- } else if (items?.DistributionSummary) {
299
- summaries = Array.isArray(items.DistributionSummary) ? items.DistributionSummary : [items.DistributionSummary];
300
- }
301
- distributions.push(...summaries.map((item) => ({
302
- Id: item.Id,
303
- ARN: item.ARN,
304
- Status: item.Status,
305
- DomainName: item.DomainName,
306
- Aliases: item.Aliases || undefined,
307
- Enabled: item.Enabled === "true" || item.Enabled === true
308
- })));
309
- return distributions;
310
- }
311
- async getDistribution(distributionId) {
312
- const result = await this.client.request({
313
- service: "cloudfront",
314
- region: "us-east-1",
315
- method: "GET",
316
- path: `/2020-05-31/distribution/${distributionId}`
317
- });
318
- const dist = result.Distribution || result;
319
- return {
320
- Id: dist.Id,
321
- ARN: dist.ARN,
322
- Status: dist.Status,
323
- DomainName: dist.DomainName,
324
- Aliases: dist.DistributionConfig?.Aliases?.Items || dist.Aliases?.Items || [],
325
- Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
326
- };
327
- }
328
- async getDistributionConfig(distributionId) {
329
- const result = await this.client.request({
330
- service: "cloudfront",
331
- region: "us-east-1",
332
- method: "GET",
333
- path: `/2020-05-31/distribution/${distributionId}/config`
334
- });
335
- return {
336
- ETag: result.ETag || "",
337
- DistributionConfig: result.DistributionConfig || result
338
- };
339
- }
340
- async invalidateAll(distributionId) {
341
- return this.createInvalidation({
342
- distributionId,
343
- paths: ["/*"]
344
- });
345
- }
346
- async invalidatePaths(distributionId, paths) {
347
- const formattedPaths = paths.map((path) => path.startsWith("/") ? path : `/${path}`);
348
- return this.createInvalidation({
349
- distributionId,
350
- paths: formattedPaths
351
- });
352
- }
353
- async invalidatePattern(distributionId, pattern) {
354
- const path = pattern.startsWith("/") ? pattern : `/${pattern}`;
355
- return this.createInvalidation({
356
- distributionId,
357
- paths: [path]
358
- });
359
- }
360
- async invalidateAfterDeployment(options) {
361
- const { distributionId, changedPaths, invalidateAll = false, wait = false } = options;
362
- let result;
363
- if (invalidateAll || !changedPaths || changedPaths.length === 0) {
364
- result = await this.invalidateAll(distributionId);
365
- } else {
366
- result = await this.invalidatePaths(distributionId, changedPaths);
367
- }
368
- if (wait) {
369
- await this.waitForInvalidation(distributionId, result.Id);
370
- }
371
- return {
372
- invalidationId: result.Id,
373
- status: result.Status
374
- };
375
- }
376
- async findDistributionByDomain(domain) {
377
- const distributions = await this.listDistributions();
378
- const found = distributions.find((dist) => {
379
- if (dist.DomainName === domain) {
380
- return true;
381
- }
382
- if (dist.Aliases?.Items && dist.Aliases.Items.includes(domain)) {
383
- return true;
384
- }
385
- return false;
386
- });
387
- return found || null;
388
- }
389
- async batchInvalidate(distributionIds, paths = ["/*"]) {
390
- const results = await Promise.all(distributionIds.map(async (distributionId) => {
391
- const result = await this.createInvalidation({
392
- distributionId,
393
- paths
394
- });
395
- return {
396
- distributionId,
397
- invalidationId: result.Id,
398
- status: result.Status
399
- };
400
- }));
401
- return results;
402
- }
403
- async updateCustomErrorResponses(options) {
404
- const { distributionId, customErrorResponses } = options;
405
- const getResult = await this.client.request({
406
- service: "cloudfront",
407
- region: "us-east-1",
408
- method: "GET",
409
- path: `/2020-05-31/distribution/${distributionId}/config`,
410
- returnHeaders: true
411
- });
412
- const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
413
- const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
414
- if (!currentConfig || currentConfig.Enabled === undefined) {
415
- throw new Error("Failed to get current distribution config");
416
- }
417
- if (customErrorResponses.length === 0) {
418
- currentConfig.CustomErrorResponses = {
419
- Quantity: 0
420
- };
421
- } else {
422
- currentConfig.CustomErrorResponses = {
423
- Quantity: customErrorResponses.length,
424
- Items: {
425
- CustomErrorResponse: customErrorResponses.map((err) => ({
426
- ErrorCode: err.errorCode,
427
- ...err.responsePagePath && { ResponsePagePath: err.responsePagePath },
428
- ...err.responseCode && { ResponseCode: err.responseCode },
429
- ...err.errorCachingMinTTL !== undefined && { ErrorCachingMinTTL: err.errorCachingMinTTL }
430
- }))
431
- }
432
- };
433
- }
434
- const configXml = this.buildDistributionConfigXml(currentConfig);
435
- const result = await this.client.request({
436
- service: "cloudfront",
437
- region: "us-east-1",
438
- method: "PUT",
439
- path: `/2020-05-31/distribution/${distributionId}/config`,
440
- body: configXml,
441
- headers: {
442
- "Content-Type": "application/xml",
443
- "If-Match": etag
444
- }
445
- });
446
- const dist = result.Distribution || result;
447
- return {
448
- Distribution: {
449
- Id: dist.Id,
450
- ARN: dist.ARN,
451
- Status: dist.Status,
452
- DomainName: dist.DomainName,
453
- Aliases: dist.DistributionConfig?.Aliases?.Items || [],
454
- Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
455
- },
456
- ETag: result.ETag || ""
457
- };
458
- }
459
- async removeCustomErrorResponses(distributionId) {
460
- return this.updateCustomErrorResponses({
461
- distributionId,
462
- customErrorResponses: []
463
- });
464
- }
465
- async updateDistribution(options) {
466
- const { distributionId, aliases, certificateArn, comment } = options;
467
- const getResult = await this.client.request({
468
- service: "cloudfront",
469
- region: "us-east-1",
470
- method: "GET",
471
- path: `/2020-05-31/distribution/${distributionId}/config`,
472
- returnHeaders: true
473
- });
474
- const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
475
- const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
476
- if (!currentConfig || currentConfig.Enabled === undefined) {
477
- throw new Error("Failed to get current distribution config");
478
- }
479
- if (aliases && aliases.length > 0) {
480
- currentConfig.Aliases = {
481
- Quantity: aliases.length,
482
- Items: { Item: aliases }
483
- };
484
- }
485
- if (certificateArn) {
486
- currentConfig.ViewerCertificate = {
487
- ACMCertificateArn: certificateArn,
488
- SSLSupportMethod: "sni-only",
489
- MinimumProtocolVersion: "TLSv1.2_2021",
490
- CertificateSource: "acm"
491
- };
492
- }
493
- if (comment) {
494
- currentConfig.Comment = comment;
495
- }
496
- const configXml = this.buildDistributionConfigXml(currentConfig);
497
- const result = await this.client.request({
498
- service: "cloudfront",
499
- region: "us-east-1",
500
- method: "PUT",
501
- path: `/2020-05-31/distribution/${distributionId}/config`,
502
- body: configXml,
503
- headers: {
504
- "Content-Type": "application/xml",
505
- "If-Match": etag
506
- }
507
- });
508
- const dist = result.Distribution || result;
509
- return {
510
- Distribution: {
511
- Id: dist.Id,
512
- ARN: dist.ARN,
513
- Status: dist.Status,
514
- DomainName: dist.DomainName,
515
- Aliases: aliases ? { Quantity: aliases.length, Items: aliases } : { Quantity: 0, Items: [] },
516
- Enabled: dist.DistributionConfig?.Enabled === "true" || dist.DistributionConfig?.Enabled === true
517
- },
518
- ETag: result.ETag || ""
519
- };
520
- }
521
- buildDistributionConfigXml(config) {
522
- const escapeXml = (str) => {
523
- return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
524
- };
525
- const arrayChildNames = {
526
- Items: "",
527
- Methods: "Method",
528
- Headers: "Name",
529
- Cookies: "Name",
530
- QueryStringCacheKeys: "Name",
531
- TrustedKeyGroups: "KeyGroup",
532
- TrustedSigners: "AwsAccountNumber",
533
- LambdaFunctionAssociations: "LambdaFunctionAssociation",
534
- FunctionAssociations: "FunctionAssociation",
535
- CacheBehaviors: "CacheBehavior",
536
- CustomErrorResponses: "CustomErrorResponse",
537
- GeoRestriction: "Location"
538
- };
539
- const itemsChildNames = {
540
- Origins: "Origin",
541
- Aliases: "CNAME",
542
- AllowedMethods: "Method",
543
- CachedMethods: "Method",
544
- CustomErrorResponses: "CustomErrorResponse",
545
- CacheBehaviors: "CacheBehavior"
546
- };
547
- const buildXmlElement = (name, value, indent = "", parentContext = "") => {
548
- if (value === null || value === undefined) {
549
- return "";
550
- }
551
- if (name.startsWith("@_") || name === "?xml") {
552
- return "";
553
- }
554
- if (typeof value === "boolean") {
555
- return `${indent}<${name}>${value}</${name}>
556
- `;
557
- }
558
- if (typeof value === "number" || typeof value === "string") {
559
- return `${indent}<${name}>${escapeXml(String(value))}</${name}>
560
- `;
561
- }
562
- if (Array.isArray(value)) {
563
- const childName = arrayChildNames[name] || name.replace(/s$/, "");
564
- return value.map((item) => buildXmlElement(childName, item, indent, name)).join("");
565
- }
566
- if (typeof value === "object") {
567
- if (name === "Items") {
568
- const childElementName = itemsChildNames[parentContext] || "";
569
- const keys = Object.keys(value).filter((k) => !k.startsWith("@_"));
570
- if (keys.length === 1 && !Array.isArray(value[keys[0]])) {
571
- const childKey = keys[0];
572
- const childValue = value[childKey];
573
- if (typeof childValue === "string") {
574
- return `${indent}<Items>
575
- ${indent} <${childKey}>${escapeXml(childValue)}</${childKey}>
576
- ${indent}</Items>
577
- `;
578
- } else if (typeof childValue === "object" && !Array.isArray(childValue)) {
579
- return `${indent}<Items>
580
- ${buildXmlElement(childKey, childValue, indent + " ", name)}${indent}</Items>
581
- `;
582
- }
583
- }
584
- if (keys.length === 1 && Array.isArray(value[keys[0]])) {
585
- const childKey = keys[0];
586
- const childArray = value[childKey];
587
- let children2 = "";
588
- for (const item of childArray) {
589
- if (typeof item === "string") {
590
- children2 += `${indent} <${childKey}>${escapeXml(item)}</${childKey}>
591
- `;
592
- } else {
593
- children2 += buildXmlElement(childKey, item, indent + " ", name);
594
- }
595
- }
596
- return `${indent}<Items>
597
- ${children2}${indent}</Items>
598
- `;
599
- }
600
- if (Array.isArray(value)) {
601
- let children2 = "";
602
- const childName = childElementName || "Item";
603
- for (const item of value) {
604
- if (typeof item === "string") {
605
- children2 += `${indent} <${childName}>${escapeXml(item)}</${childName}>
606
- `;
607
- } else {
608
- children2 += buildXmlElement(childName, item, indent + " ", name);
609
- }
610
- }
611
- return `${indent}<Items>
612
- ${children2}${indent}</Items>
613
- `;
614
- }
615
- }
616
- let children = "";
617
- for (const [key, val] of Object.entries(value)) {
618
- if (!key.startsWith("@_")) {
619
- children += buildXmlElement(key, val, indent + " ", name);
620
- }
621
- }
622
- if (children === "") {
623
- return `${indent}<${name}/>
624
- `;
625
- }
626
- return `${indent}<${name}>
627
- ${children}${indent}</${name}>
628
- `;
629
- }
630
- return "";
631
- };
632
- return `<?xml version="1.0" encoding="UTF-8"?>
633
- <DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
634
- ${Object.entries(config).filter(([k]) => !k.startsWith("@_")).map(([key, val]) => buildXmlElement(key, val, " ", "DistributionConfig")).join("")}</DistributionConfig>`;
635
- }
636
- async ensureDynamicHttpMethods(distributionId) {
637
- const getResult = await this.client.request({
638
- service: "cloudfront",
639
- region: "us-east-1",
640
- method: "GET",
641
- path: `/2020-05-31/distribution/${distributionId}/config`,
642
- returnHeaders: true
643
- });
644
- const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
645
- const currentConfig = getResult.body?.DistributionConfig || getResult.DistributionConfig || getResult.body;
646
- if (!currentConfig) {
647
- throw new Error("Failed to get current distribution config");
648
- }
649
- let changed = false;
650
- const dynamicMethods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"];
651
- const parseAllowedMethods = (allowedMethods) => {
652
- if (!allowedMethods)
653
- return [];
654
- const items = allowedMethods.Items;
655
- if (!items)
656
- return [];
657
- if (Array.isArray(items))
658
- return items.map(String);
659
- if (typeof items === "object" && items.Method) {
660
- const method = items.Method;
661
- return Array.isArray(method) ? method.map(String) : [String(method)];
662
- }
663
- if (items.Item) {
664
- return Array.isArray(items.Item) ? items.Item.map(String) : [String(items.Item)];
665
- }
666
- return [];
667
- };
668
- const defaultBehavior = currentConfig.DefaultCacheBehavior;
669
- if (defaultBehavior) {
670
- const allowed = parseAllowedMethods(defaultBehavior.AllowedMethods);
671
- if (!allowed.includes("POST")) {
672
- defaultBehavior.AllowedMethods = {
673
- Quantity: dynamicMethods.length,
674
- Items: { Method: dynamicMethods },
675
- CachedMethods: {
676
- Quantity: 2,
677
- Items: { Method: ["GET", "HEAD"] }
678
- }
679
- };
680
- changed = true;
681
- }
682
- }
683
- if (!changed) {
684
- return false;
685
- }
686
- const configXml = this.buildDistributionConfigXml(currentConfig);
687
- await this.client.request({
688
- service: "cloudfront",
689
- region: "us-east-1",
690
- method: "PUT",
691
- path: `/2020-05-31/distribution/${distributionId}/config`,
692
- body: configXml,
693
- headers: {
694
- "Content-Type": "application/xml",
695
- "If-Match": etag
696
- }
697
- });
698
- return true;
699
- }
700
- async addAliases(distributionId, aliases, certificateArn) {
701
- return this.updateDistribution({
702
- distributionId,
703
- aliases,
704
- certificateArn
705
- });
706
- }
707
- async createFunction(options) {
708
- const { name, code, comment = "", runtime = "cloudfront-js-2.0" } = options;
709
- const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
710
- <CreateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
711
- <Name>${name}</Name>
712
- <FunctionConfig>
713
- <Comment>${comment}</Comment>
714
- <Runtime>${runtime}</Runtime>
715
- </FunctionConfig>
716
- <FunctionCode>${Buffer.from(code).toString("base64")}</FunctionCode>
717
- </CreateFunctionRequest>`;
718
- const result = await this.client.request({
719
- service: "cloudfront",
720
- region: "us-east-1",
721
- method: "POST",
722
- path: "/2020-05-31/function",
723
- body: functionXml,
724
- headers: {
725
- "Content-Type": "application/xml"
726
- }
727
- });
728
- const func = result.FunctionSummary || result;
729
- return {
730
- FunctionARN: func.FunctionMetadata?.FunctionARN || func.FunctionARN,
731
- Name: func.Name || name,
732
- Stage: func.FunctionMetadata?.Stage || "DEVELOPMENT",
733
- ETag: result.ETag || ""
734
- };
735
- }
736
- async listFunctions() {
737
- const result = await this.client.request({
738
- service: "cloudfront",
739
- region: "us-east-1",
740
- method: "GET",
741
- path: "/2020-05-31/function"
742
- });
743
- const functions = [];
744
- const items = result.FunctionList?.Items?.FunctionSummary;
745
- if (items) {
746
- const list = Array.isArray(items) ? items : [items];
747
- for (const item of list) {
748
- functions.push({
749
- Name: item.Name,
750
- FunctionARN: item.FunctionMetadata?.FunctionARN,
751
- Stage: item.FunctionMetadata?.Stage,
752
- CreatedTime: item.FunctionMetadata?.CreatedTime,
753
- LastModifiedTime: item.FunctionMetadata?.LastModifiedTime
754
- });
755
- }
756
- }
757
- return functions;
758
- }
759
- async getFunction(name, stage = "LIVE") {
760
- try {
761
- const result = await this.client.request({
762
- service: "cloudfront",
763
- region: "us-east-1",
764
- method: "GET",
765
- path: `/2020-05-31/function/${name}`,
766
- queryParams: { Stage: stage },
767
- returnHeaders: true
768
- });
769
- const func = result.body?.FunctionSummary || result.FunctionSummary || result.body || result;
770
- return {
771
- FunctionARN: func.FunctionMetadata?.FunctionARN,
772
- Name: func.Name || name,
773
- Stage: func.FunctionMetadata?.Stage || stage,
774
- ETag: result.headers?.etag || result.ETag || "",
775
- FunctionCode: func.FunctionCode
776
- };
777
- } catch (err) {
778
- if (err.message?.includes("404") || err.message?.includes("NoSuchFunctionExists")) {
779
- return null;
780
- }
781
- throw err;
782
- }
783
- }
784
- async publishFunction(nameOrOptions, etag) {
785
- let name;
786
- let functionETag;
787
- if (typeof nameOrOptions === "object") {
788
- name = nameOrOptions.Name;
789
- functionETag = nameOrOptions.IfMatch;
790
- } else {
791
- name = nameOrOptions;
792
- functionETag = etag;
793
- }
794
- if (!functionETag) {
795
- const func2 = await this.getFunction(name, "DEVELOPMENT");
796
- if (!func2) {
797
- throw new Error(`Function ${name} not found`);
798
- }
799
- functionETag = func2.ETag;
800
- }
801
- const result = await this.client.request({
802
- service: "cloudfront",
803
- region: "us-east-1",
804
- method: "POST",
805
- path: `/2020-05-31/function/${name}/publish`,
806
- headers: {
807
- "If-Match": functionETag
808
- }
809
- });
810
- const func = result.FunctionSummary || result;
811
- return {
812
- FunctionARN: func.FunctionMetadata?.FunctionARN,
813
- Stage: func.FunctionMetadata?.Stage || "LIVE",
814
- FunctionSummary: func
815
- };
816
- }
817
- async describeFunction(options) {
818
- const { Name, Stage = "DEVELOPMENT" } = options;
819
- const result = await this.client.request({
820
- service: "cloudfront",
821
- region: "us-east-1",
822
- method: "GET",
823
- path: `/2020-05-31/function/${Name}/describe`,
824
- queryParams: { Stage },
825
- returnHeaders: true
826
- });
827
- return {
828
- ETag: result.headers?.etag || result.ETag || "",
829
- FunctionSummary: result.body?.FunctionSummary || result.FunctionSummary || result.body || result
830
- };
831
- }
832
- async updateFunction(options) {
833
- const { Name, FunctionCode, FunctionConfig, IfMatch } = options;
834
- const functionXml = `<?xml version="1.0" encoding="UTF-8"?>
835
- <UpdateFunctionRequest xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
836
- <FunctionConfig>
837
- <Comment>${FunctionConfig.Comment}</Comment>
838
- <Runtime>${FunctionConfig.Runtime}</Runtime>
839
- </FunctionConfig>
840
- <FunctionCode>${Buffer.from(FunctionCode).toString("base64")}</FunctionCode>
841
- </UpdateFunctionRequest>`;
842
- const result = await this.client.request({
843
- service: "cloudfront",
844
- region: "us-east-1",
845
- method: "PUT",
846
- path: `/2020-05-31/function/${Name}`,
847
- body: functionXml,
848
- headers: {
849
- "Content-Type": "application/xml",
850
- "If-Match": IfMatch
851
- },
852
- returnHeaders: true
853
- });
854
- return {
855
- ETag: result.headers?.etag || result.ETag || "",
856
- FunctionSummary: result.body?.FunctionSummary || result.FunctionSummary || result.body || result
857
- };
858
- }
859
- async deleteFunction(name, etag) {
860
- let functionETag = etag;
861
- if (!functionETag) {
862
- const func = await this.getFunction(name, "DEVELOPMENT");
863
- if (!func) {
864
- return;
865
- }
866
- functionETag = func.ETag;
867
- }
868
- await this.client.request({
869
- service: "cloudfront",
870
- region: "us-east-1",
871
- method: "DELETE",
872
- path: `/2020-05-31/function/${name}`,
873
- headers: {
874
- "If-Match": functionETag
875
- }
876
- });
877
- }
878
- async createIndexRewriteFunction(name) {
879
- const code = `function handler(event) {
880
- const request = event.request;
881
- var uri = request.uri;
882
-
883
- // Check if the request is for a directory (ends with /)
884
- if (uri.endsWith('/')) {
885
- request.uri += 'index.html';
886
- }
887
- // Check if the request doesn't have a file extension
888
- else if (!uri.includes('.')) {
889
- // Add trailing slash to redirect to directory
890
- request.uri += '/index.html';
891
- }
892
-
893
- return request;
894
- }`;
895
- return this.createFunction({
896
- name,
897
- code,
898
- comment: "Rewrite directory requests to index.html for S3 static sites",
899
- runtime: "cloudfront-js-2.0"
900
- });
901
- }
902
- async listOriginAccessControls() {
903
- const result = await this.client.request({
904
- service: "cloudfront",
905
- region: "us-east-1",
906
- method: "GET",
907
- path: "/2020-05-31/origin-access-control"
908
- });
909
- const items = [];
910
- if (result.OriginAccessControlList?.Items?.OriginAccessControlSummary) {
911
- const summaries = Array.isArray(result.OriginAccessControlList.Items.OriginAccessControlSummary) ? result.OriginAccessControlList.Items.OriginAccessControlSummary : [result.OriginAccessControlList.Items.OriginAccessControlSummary];
912
- items.push(...summaries.map((item) => ({
913
- Id: item.Id,
914
- Name: item.Name,
915
- Description: item.Description,
916
- SigningProtocol: item.SigningProtocol,
917
- SigningBehavior: item.SigningBehavior,
918
- OriginAccessControlOriginType: item.OriginAccessControlOriginType
919
- })));
920
- }
921
- return items;
922
- }
923
- async createOriginAccessControl(options) {
924
- const {
925
- name,
926
- description = `OAC for ${name}`,
927
- signingProtocol = "sigv4",
928
- signingBehavior = "always",
929
- originType = "s3"
930
- } = options;
931
- const body = `<?xml version="1.0" encoding="UTF-8"?>
932
- <OriginAccessControlConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
933
- <Name>${name}</Name>
934
- <Description>${description}</Description>
935
- <SigningProtocol>${signingProtocol}</SigningProtocol>
936
- <SigningBehavior>${signingBehavior}</SigningBehavior>
937
- <OriginAccessControlOriginType>${originType}</OriginAccessControlOriginType>
938
- </OriginAccessControlConfig>`;
939
- const result = await this.client.request({
940
- service: "cloudfront",
941
- region: "us-east-1",
942
- method: "POST",
943
- path: "/2020-05-31/origin-access-control",
944
- body,
945
- headers: {
946
- "Content-Type": "application/xml"
947
- },
948
- returnHeaders: true
949
- });
950
- const oac = result.body?.OriginAccessControl || result.OriginAccessControl || result.body || result;
951
- return {
952
- Id: oac.Id,
953
- Name: oac.OriginAccessControlConfig?.Name || name,
954
- Description: oac.OriginAccessControlConfig?.Description || description,
955
- SigningProtocol: oac.OriginAccessControlConfig?.SigningProtocol || signingProtocol,
956
- SigningBehavior: oac.OriginAccessControlConfig?.SigningBehavior || signingBehavior,
957
- OriginAccessControlOriginType: oac.OriginAccessControlConfig?.OriginAccessControlOriginType || originType,
958
- ETag: result.headers?.etag || result.ETag || ""
959
- };
960
- }
961
- async findOrCreateOriginAccessControl(name, originType = "s3") {
962
- const oacs = await this.listOriginAccessControls();
963
- const existing = oacs.find((oac) => oac.Name === name && oac.OriginAccessControlOriginType === originType);
964
- if (existing) {
965
- return { Id: existing.Id, Name: existing.Name, isNew: false };
966
- }
967
- const created = await this.createOriginAccessControl({ name, originType });
968
- return { Id: created.Id, Name: created.Name, isNew: true };
969
- }
970
- async createDistributionForS3(options) {
971
- const {
972
- bucketName,
973
- bucketRegion,
974
- originAccessControlId,
975
- aliases = [],
976
- certificateArn,
977
- defaultRootObject = "index.html",
978
- comment = `Distribution for ${bucketName}`,
979
- priceClass = "PriceClass_100",
980
- enabled = true,
981
- trustedKeyGroupIds = [],
982
- mediaDelivery = false
983
- } = options;
984
- const originId = `S3-${bucketName}`;
985
- const s3DomainName = `${bucketName}.s3.${bucketRegion}.amazonaws.com`;
986
- const callerReference = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
987
- const trustedKeyGroupsXml = trustedKeyGroupIds.length > 0 ? `<TrustedKeyGroups>
988
- <Enabled>true</Enabled>
989
- <Quantity>${trustedKeyGroupIds.length}</Quantity>
990
- <Items>${trustedKeyGroupIds.map((id) => `<KeyGroup>${id}</KeyGroup>`).join("")}</Items>
991
- </TrustedKeyGroups>` : `<TrustedKeyGroups><Enabled>false</Enabled><Quantity>0</Quantity></TrustedKeyGroups>`;
992
- let aliasesXml = "<Aliases><Quantity>0</Quantity></Aliases>";
993
- if (aliases.length > 0) {
994
- aliasesXml = `<Aliases>
995
- <Quantity>${aliases.length}</Quantity>
996
- <Items>
997
- ${aliases.map((a) => `<CNAME>${a}</CNAME>`).join(`
998
- `)}
999
- </Items>
1000
- </Aliases>`;
1001
- }
1002
- let viewerCertificateXml = `<ViewerCertificate>
1003
- <CloudFrontDefaultCertificate>true</CloudFrontDefaultCertificate>
1004
- </ViewerCertificate>`;
1005
- if (certificateArn && aliases.length > 0) {
1006
- viewerCertificateXml = `<ViewerCertificate>
1007
- <ACMCertificateArn>${certificateArn}</ACMCertificateArn>
1008
- <SSLSupportMethod>sni-only</SSLSupportMethod>
1009
- <MinimumProtocolVersion>TLSv1.2_2021</MinimumProtocolVersion>
1010
- <CertificateSource>acm</CertificateSource>
1011
- </ViewerCertificate>`;
1012
- }
1013
- const body = `<?xml version="1.0" encoding="UTF-8"?>
1014
- <DistributionConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
1015
- <CallerReference>${callerReference}</CallerReference>
1016
- <Comment>${comment}</Comment>
1017
- <DefaultRootObject>${defaultRootObject}</DefaultRootObject>
1018
- <Origins>
1019
- <Quantity>1</Quantity>
1020
- <Items>
1021
- <Origin>
1022
- <Id>${originId}</Id>
1023
- <DomainName>${s3DomainName}</DomainName>
1024
- <OriginPath></OriginPath>
1025
- <S3OriginConfig>
1026
- <OriginAccessIdentity></OriginAccessIdentity>
1027
- </S3OriginConfig>
1028
- <OriginAccessControlId>${originAccessControlId}</OriginAccessControlId>
1029
- </Origin>
1030
- </Items>
1031
- </Origins>
1032
- <DefaultCacheBehavior>
1033
- <TargetOriginId>${originId}</TargetOriginId>
1034
- <ViewerProtocolPolicy>redirect-to-https</ViewerProtocolPolicy>
1035
- <AllowedMethods>
1036
- <Quantity>2</Quantity>
1037
- <Items>
1038
- <Method>GET</Method>
1039
- <Method>HEAD</Method>
1040
- </Items>
1041
- <CachedMethods>
1042
- <Quantity>2</Quantity>
1043
- <Items>
1044
- <Method>GET</Method>
1045
- <Method>HEAD</Method>
1046
- </Items>
1047
- </CachedMethods>
1048
- </AllowedMethods>
1049
- <Compress>true</Compress>
1050
- <CachePolicyId>658327ea-f89d-4fab-a63d-7e88639e58f6</CachePolicyId>
1051
- ${trustedKeyGroupsXml}
1052
- </DefaultCacheBehavior>
1053
- ${aliasesXml}
1054
- ${viewerCertificateXml}
1055
- <PriceClass>${priceClass}</PriceClass>
1056
- <Enabled>${enabled}</Enabled>
1057
- <HttpVersion>http2and3</HttpVersion>
1058
- <IsIPV6Enabled>true</IsIPV6Enabled>
1059
- ${mediaDelivery ? "<CustomErrorResponses><Quantity>0</Quantity></CustomErrorResponses>" : `<CustomErrorResponses>
1060
- <Quantity>1</Quantity>
1061
- <Items>
1062
- <CustomErrorResponse>
1063
- <ErrorCode>403</ErrorCode>
1064
- <ResponsePagePath>/index.html</ResponsePagePath>
1065
- <ResponseCode>200</ResponseCode>
1066
- <ErrorCachingMinTTL>300</ErrorCachingMinTTL>
1067
- </CustomErrorResponse>
1068
- </Items>
1069
- </CustomErrorResponses>`}
1070
- </DistributionConfig>`;
1071
- const result = await this.client.request({
1072
- service: "cloudfront",
1073
- region: "us-east-1",
1074
- method: "POST",
1075
- path: "/2020-05-31/distribution",
1076
- body,
1077
- headers: {
1078
- "Content-Type": "application/xml"
1079
- },
1080
- returnHeaders: true
1081
- });
1082
- const dist = result.body?.Distribution || result.Distribution || result.body || result;
1083
- return {
1084
- Id: dist.Id,
1085
- ARN: dist.ARN,
1086
- DomainName: dist.DomainName,
1087
- Status: dist.Status,
1088
- ETag: result.headers?.etag || result.ETag || ""
1089
- };
1090
- }
1091
- async createMediaDistributionForS3(options) {
1092
- return this.createDistributionForS3({
1093
- ...options,
1094
- defaultRootObject: "",
1095
- mediaDelivery: true,
1096
- comment: options.comment ?? `Media distribution for ${options.bucketName}`
1097
- });
1098
- }
1099
- async listPublicKeys() {
1100
- const result = await this.client.request({
1101
- service: "cloudfront",
1102
- region: "us-east-1",
1103
- method: "GET",
1104
- path: "/2020-05-31/public-key"
1105
- });
1106
- const values = this.collectionItems(result.PublicKeyList ?? result, "PublicKeySummary");
1107
- return values.map((value) => ({
1108
- Id: String(value.Id),
1109
- Name: String(value.Name),
1110
- Comment: value.Comment ? String(value.Comment) : undefined
1111
- }));
1112
- }
1113
- async createPublicKey(options) {
1114
- if (!/^[A-Za-z0-9_-]{1,128}$/.test(options.name))
1115
- throw new TypeError("CloudFront public key name is invalid");
1116
- if (!options.encodedKey.includes("BEGIN PUBLIC KEY"))
1117
- throw new TypeError("CloudFront public key must be PEM encoded");
1118
- const escape = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1119
- const result = await this.client.request({
1120
- service: "cloudfront",
1121
- region: "us-east-1",
1122
- method: "POST",
1123
- path: "/2020-05-31/public-key",
1124
- headers: { "Content-Type": "application/xml" },
1125
- body: `<PublicKeyConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/"><CallerReference>${escape(options.callerReference ?? `${Date.now()}-${options.name}`)}</CallerReference><Name>${escape(options.name)}</Name><EncodedKey>${escape(options.encodedKey)}</EncodedKey><Comment>${escape(options.comment ?? "")}</Comment></PublicKeyConfig>`
1126
- });
1127
- const value = result.PublicKey ?? result;
1128
- return {
1129
- Id: String(value.Id),
1130
- Name: String(value.PublicKeyConfig?.Name ?? options.name),
1131
- EncodedKey: String(value.PublicKeyConfig?.EncodedKey ?? options.encodedKey),
1132
- Comment: String(value.PublicKeyConfig?.Comment ?? options.comment ?? "")
1133
- };
1134
- }
1135
- async listKeyGroups() {
1136
- const result = await this.client.request({
1137
- service: "cloudfront",
1138
- region: "us-east-1",
1139
- method: "GET",
1140
- path: "/2020-05-31/key-group"
1141
- });
1142
- const values = this.collectionItems(result.KeyGroupList ?? result, "KeyGroupSummary");
1143
- return values.map((value) => ({
1144
- Id: String(value.KeyGroup?.Id ?? value.Id),
1145
- Name: String(value.KeyGroup?.KeyGroupConfig?.Name ?? value.KeyGroupConfig?.Name ?? value.Name),
1146
- Items: this.collectionItems(value.KeyGroup?.KeyGroupConfig?.Items ?? value.KeyGroupConfig?.Items ?? value.Items, "Item").map(String),
1147
- Comment: value.KeyGroup?.KeyGroupConfig?.Comment ?? value.KeyGroupConfig?.Comment ?? value.Comment
1148
- }));
1149
- }
1150
- async createKeyGroup(options) {
1151
- if (!/^[A-Za-z0-9_-]{1,128}$/.test(options.name))
1152
- throw new TypeError("CloudFront key group name is invalid");
1153
- const publicKeyIds = [...new Set(options.publicKeyIds)];
1154
- if (publicKeyIds.length === 0)
1155
- throw new TypeError("CloudFront key group requires at least one public key");
1156
- const escape = (value2) => value2.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1157
- const items = publicKeyIds.map((id) => `<Item>${escape(id)}</Item>`).join("");
1158
- const result = await this.client.request({
1159
- service: "cloudfront",
1160
- region: "us-east-1",
1161
- method: "POST",
1162
- path: "/2020-05-31/key-group",
1163
- headers: { "Content-Type": "application/xml" },
1164
- body: `<KeyGroupConfig xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/"><Name>${escape(options.name)}</Name><Items>${items}</Items><Comment>${escape(options.comment ?? "")}</Comment></KeyGroupConfig>`
1165
- });
1166
- const value = result.KeyGroup ?? result;
1167
- return {
1168
- Id: String(value.Id),
1169
- Name: String(value.KeyGroupConfig?.Name ?? options.name),
1170
- Items: publicKeyIds,
1171
- Comment: String(value.KeyGroupConfig?.Comment ?? options.comment ?? "")
1172
- };
1173
- }
1174
- static getS3BucketPolicyForCloudFront(bucketName, distributionArn) {
1175
- return {
1176
- Version: "2012-10-17",
1177
- Statement: [
1178
- {
1179
- Sid: "AllowCloudFrontServicePrincipal",
1180
- Effect: "Allow",
1181
- Principal: {
1182
- Service: "cloudfront.amazonaws.com"
1183
- },
1184
- Action: "s3:GetObject",
1185
- Resource: `arn:aws:s3:::${bucketName}/*`,
1186
- Condition: {
1187
- StringEquals: {
1188
- "AWS:SourceArn": distributionArn
1189
- }
1190
- }
1191
- }
1192
- ]
1193
- };
1194
- }
1195
- async waitForDistributionDeployed(distributionId, maxAttempts = 60) {
1196
- for (let i = 0;i < maxAttempts; i++) {
1197
- const dist = await this.getDistribution(distributionId);
1198
- if (dist.Status === "Deployed") {
1199
- return true;
1200
- }
1201
- await new Promise((resolve) => setTimeout(resolve, 30000));
1202
- }
1203
- return false;
1204
- }
1205
- async disableDistribution(distributionId) {
1206
- const getResult = await this.client.request({
1207
- service: "cloudfront",
1208
- region: "us-east-1",
1209
- method: "GET",
1210
- path: `/2020-05-31/distribution/${distributionId}/config`,
1211
- returnHeaders: true
1212
- });
1213
- const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
1214
- const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
1215
- if (!currentConfig || currentConfig.Enabled === undefined) {
1216
- throw new Error("Failed to get current distribution config");
1217
- }
1218
- currentConfig.Enabled = false;
1219
- const configXml = this.buildDistributionConfigXml(currentConfig);
1220
- const result = await this.client.request({
1221
- service: "cloudfront",
1222
- region: "us-east-1",
1223
- method: "PUT",
1224
- path: `/2020-05-31/distribution/${distributionId}/config`,
1225
- body: configXml,
1226
- headers: {
1227
- "Content-Type": "application/xml",
1228
- "If-Match": etag
1229
- },
1230
- returnHeaders: true
1231
- });
1232
- return { ETag: result.headers?.etag || result.headers?.ETag || result.ETag || "" };
1233
- }
1234
- async deleteDistribution(distributionId, etag) {
1235
- let etagToUse = etag || "";
1236
- if (!etagToUse) {
1237
- const getResult = await this.client.request({
1238
- service: "cloudfront",
1239
- region: "us-east-1",
1240
- method: "GET",
1241
- path: `/2020-05-31/distribution/${distributionId}`,
1242
- returnHeaders: true
1243
- });
1244
- etagToUse = getResult.headers?.etag || getResult.headers?.ETag || "";
1245
- }
1246
- await this.client.request({
1247
- service: "cloudfront",
1248
- region: "us-east-1",
1249
- method: "DELETE",
1250
- path: `/2020-05-31/distribution/${distributionId}`,
1251
- headers: {
1252
- "If-Match": etagToUse
1253
- }
1254
- });
1255
- }
1256
- async waitForDistributionDisabled(distributionId, maxAttempts = 60) {
1257
- for (let i = 0;i < maxAttempts; i++) {
1258
- const dist = await this.getDistribution(distributionId);
1259
- if (dist.Status === "Deployed" && !dist.Enabled) {
1260
- return true;
1261
- }
1262
- await new Promise((resolve) => setTimeout(resolve, 30000));
1263
- }
1264
- return false;
1265
- }
1266
- async removeAlias(distributionId, alias) {
1267
- const getResult = await this.client.request({
1268
- service: "cloudfront",
1269
- region: "us-east-1",
1270
- method: "GET",
1271
- path: `/2020-05-31/distribution/${distributionId}/config`,
1272
- returnHeaders: true
1273
- });
1274
- const etag = getResult.headers?.etag || getResult.headers?.ETag || "";
1275
- const currentConfig = getResult.body?.DistributionConfig || (getResult.body?.Enabled !== undefined ? getResult.body : undefined) || getResult.DistributionConfig;
1276
- if (!currentConfig || currentConfig.Enabled === undefined) {
1277
- throw new Error("Failed to get current distribution config");
1278
- }
1279
- let items = [];
1280
- if (currentConfig.Aliases?.Items) {
1281
- if (Array.isArray(currentConfig.Aliases.Items)) {
1282
- items = currentConfig.Aliases.Items;
1283
- } else if (typeof currentConfig.Aliases.Items === "object") {
1284
- const cname = currentConfig.Aliases.Items.CNAME;
1285
- if (typeof cname === "string") {
1286
- items = [cname];
1287
- } else if (Array.isArray(cname)) {
1288
- items = cname;
1289
- }
1290
- }
1291
- }
1292
- if (items.length === 0) {
1293
- throw new Error(`Distribution has no aliases to remove`);
1294
- }
1295
- const newItems = items.filter((a) => a !== alias);
1296
- if (newItems.length === items.length) {
1297
- throw new Error(`Alias ${alias} not found in distribution`);
1298
- }
1299
- currentConfig.Aliases.Quantity = newItems.length;
1300
- currentConfig.Aliases.Items = newItems.length > 0 ? newItems : undefined;
1301
- if (newItems.length === 0) {
1302
- currentConfig.ViewerCertificate = {
1303
- CloudFrontDefaultCertificate: true,
1304
- MinimumProtocolVersion: "TLSv1.2_2021"
1305
- };
1306
- }
1307
- const configXml = this.buildDistributionConfigXml(currentConfig);
1308
- const result = await this.client.request({
1309
- service: "cloudfront",
1310
- region: "us-east-1",
1311
- method: "PUT",
1312
- path: `/2020-05-31/distribution/${distributionId}/config`,
1313
- body: configXml,
1314
- headers: {
1315
- "Content-Type": "application/xml",
1316
- "If-Match": etag
1317
- },
1318
- returnHeaders: true
1319
- });
1320
- return { ETag: result.headers?.etag || result.headers?.ETag || result.ETag || "" };
1321
- }
1322
- }
1323
-
1324
- export { CloudFrontClient };