@projectdochelp/s3te 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +442 -0
- package/bin/s3te.mjs +2 -0
- package/package.json +66 -0
- package/packages/aws-adapter/src/aws-cli.mjs +102 -0
- package/packages/aws-adapter/src/deploy.mjs +433 -0
- package/packages/aws-adapter/src/features.mjs +16 -0
- package/packages/aws-adapter/src/index.mjs +7 -0
- package/packages/aws-adapter/src/manifest.mjs +88 -0
- package/packages/aws-adapter/src/package.mjs +323 -0
- package/packages/aws-adapter/src/runtime/common.mjs +917 -0
- package/packages/aws-adapter/src/runtime/content-mirror.mjs +301 -0
- package/packages/aws-adapter/src/runtime/invalidation-executor.mjs +61 -0
- package/packages/aws-adapter/src/runtime/invalidation-scheduler.mjs +59 -0
- package/packages/aws-adapter/src/runtime/render-worker.mjs +83 -0
- package/packages/aws-adapter/src/runtime/source-dispatcher.mjs +106 -0
- package/packages/aws-adapter/src/template.mjs +578 -0
- package/packages/aws-adapter/src/zip.mjs +111 -0
- package/packages/cli/bin/s3te.mjs +383 -0
- package/packages/cli/src/fs-adapters.mjs +221 -0
- package/packages/cli/src/project.mjs +535 -0
- package/packages/core/src/config.mjs +464 -0
- package/packages/core/src/content-query.mjs +176 -0
- package/packages/core/src/errors.mjs +14 -0
- package/packages/core/src/index.mjs +24 -0
- package/packages/core/src/mime.mjs +29 -0
- package/packages/core/src/minify.mjs +82 -0
- package/packages/core/src/render.mjs +537 -0
- package/packages/testkit/src/index.mjs +136 -0
- package/src/index.mjs +3 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { createAwsClients, invokeLambdaEvent } from "./common.mjs";
|
|
2
|
+
|
|
3
|
+
function escapeHtml(value) {
|
|
4
|
+
return String(value)
|
|
5
|
+
.replace(/&/g, "&")
|
|
6
|
+
.replace(/</g, "<")
|
|
7
|
+
.replace(/>/g, ">")
|
|
8
|
+
.replace(/\"/g, """)
|
|
9
|
+
.replace(/'/g, "'");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function renderInlineTextNode(node) {
|
|
13
|
+
let value = escapeHtml(node.text ?? "");
|
|
14
|
+
const format = Number(node.format ?? 0);
|
|
15
|
+
|
|
16
|
+
if (format & 1) {
|
|
17
|
+
value = `<b>${value}</b>`;
|
|
18
|
+
}
|
|
19
|
+
if (format & 2) {
|
|
20
|
+
value = `<i>${value}</i>`;
|
|
21
|
+
}
|
|
22
|
+
if (format & 8) {
|
|
23
|
+
value = `<u>${value}</u>`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function renderRichTextChildren(children = []) {
|
|
30
|
+
return children.map((child) => renderRichTextNode(child)).join("");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function renderRichTextAttributes(node) {
|
|
34
|
+
const attributes = [];
|
|
35
|
+
if (node.className) {
|
|
36
|
+
attributes.push(` class="${escapeHtml(node.className)}"`);
|
|
37
|
+
}
|
|
38
|
+
if (node.format && typeof node.format === "string") {
|
|
39
|
+
attributes.push(` style="text-align:${escapeHtml(node.format)}"`);
|
|
40
|
+
}
|
|
41
|
+
return attributes.join("");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function renderRichTextNode(node) {
|
|
45
|
+
if (!node || typeof node !== "object") {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (node.type === "root") {
|
|
50
|
+
return renderRichTextChildren(node.children);
|
|
51
|
+
}
|
|
52
|
+
if (node.type === "text") {
|
|
53
|
+
return renderInlineTextNode(node);
|
|
54
|
+
}
|
|
55
|
+
if (node.type === "linebreak") {
|
|
56
|
+
return "<br>";
|
|
57
|
+
}
|
|
58
|
+
if (node.type === "delimiter") {
|
|
59
|
+
return "<hr>";
|
|
60
|
+
}
|
|
61
|
+
if (node.type === "paragraph-element") {
|
|
62
|
+
return `<p${renderRichTextAttributes(node)}>${renderRichTextChildren(node.children)}</p>`;
|
|
63
|
+
}
|
|
64
|
+
if (node.type === "heading-element") {
|
|
65
|
+
const tag = /^h[1-6]$/i.test(node.tag) ? node.tag.toLowerCase() : "h2";
|
|
66
|
+
return `<${tag}${renderRichTextAttributes(node)}>${renderRichTextChildren(node.children)}</${tag}>`;
|
|
67
|
+
}
|
|
68
|
+
if (node.type === "webiny-list") {
|
|
69
|
+
const tag = node.listType === "number" || node.format === "number" ? "ol" : "ul";
|
|
70
|
+
return `<${tag}>${renderRichTextChildren(node.children)}</${tag}>`;
|
|
71
|
+
}
|
|
72
|
+
if (node.type === "webiny-listitem") {
|
|
73
|
+
return `<li>${renderRichTextChildren(node.children)}</li>`;
|
|
74
|
+
}
|
|
75
|
+
if (node.type === "link") {
|
|
76
|
+
const href = node.url ?? node.href ?? "#";
|
|
77
|
+
const target = node.target ? ` target="${escapeHtml(node.target)}"` : "";
|
|
78
|
+
return `<a href="${escapeHtml(href)}"${target}>${renderRichTextChildren(node.children)}</a>`;
|
|
79
|
+
}
|
|
80
|
+
if (node.type === "image") {
|
|
81
|
+
const src = node.src ?? node.url ?? node.file?.src ?? node.file?.url ?? "";
|
|
82
|
+
if (!src) {
|
|
83
|
+
return "";
|
|
84
|
+
}
|
|
85
|
+
const alt = escapeHtml(node.altText ?? node.alt ?? "");
|
|
86
|
+
const caption = node.caption ? `<figcaption>${escapeHtml(node.caption)}</figcaption>` : "";
|
|
87
|
+
return `<figure><img src="${escapeHtml(src)}" alt="${alt}">${caption}</figure>`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return renderRichTextChildren(node.children);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function serializeStructuredValue(value) {
|
|
94
|
+
if (value && typeof value === "object") {
|
|
95
|
+
if (Array.isArray(value.root?.children)) {
|
|
96
|
+
return renderRichTextNode(value.root);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof value.type === "string" && Array.isArray(value.children)) {
|
|
100
|
+
return renderRichTextNode(value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function toSimpleValue(value) {
|
|
108
|
+
if (value == null) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (Array.isArray(value)) {
|
|
117
|
+
return value.map((entry) => String(toSimpleValue(entry)));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (typeof value === "object") {
|
|
121
|
+
if (typeof value.html === "string") {
|
|
122
|
+
return value.html;
|
|
123
|
+
}
|
|
124
|
+
if (typeof value.text === "string") {
|
|
125
|
+
return value.text;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const structured = serializeStructuredValue(value);
|
|
129
|
+
if (structured !== null) {
|
|
130
|
+
return structured;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return String(value);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function normalizeValues(item) {
|
|
138
|
+
const valueSource = item.values && typeof item.values === "object"
|
|
139
|
+
? item.values
|
|
140
|
+
: (item.data && typeof item.data === "object" && !Array.isArray(item.data) ? item.data : null);
|
|
141
|
+
|
|
142
|
+
if (valueSource) {
|
|
143
|
+
return Object.fromEntries(Object.entries(valueSource).map(([key, value]) => [key, toSimpleValue(value)]));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const reserved = new Set([
|
|
147
|
+
"id",
|
|
148
|
+
"entryId",
|
|
149
|
+
"contentId",
|
|
150
|
+
"contentid",
|
|
151
|
+
"model",
|
|
152
|
+
"modelId",
|
|
153
|
+
"__typename",
|
|
154
|
+
"locale",
|
|
155
|
+
"localeCode",
|
|
156
|
+
"tenant",
|
|
157
|
+
"tenantId",
|
|
158
|
+
"data",
|
|
159
|
+
"createdAt",
|
|
160
|
+
"createdOn",
|
|
161
|
+
"updatedAt",
|
|
162
|
+
"savedOn",
|
|
163
|
+
"publishedOn",
|
|
164
|
+
"_version",
|
|
165
|
+
"_lastChangedAt",
|
|
166
|
+
"status",
|
|
167
|
+
"published"
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const values = {};
|
|
171
|
+
for (const [key, value] of Object.entries(item)) {
|
|
172
|
+
if (reserved.has(key) || key.startsWith("_")) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
values[key] = toSimpleValue(value);
|
|
176
|
+
}
|
|
177
|
+
return values;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function isPublished(item) {
|
|
181
|
+
return item.status === "published"
|
|
182
|
+
|| item.published === true
|
|
183
|
+
|| item.isPublished === true
|
|
184
|
+
|| item.publishedOn != null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function extractWebinyLocale(item) {
|
|
188
|
+
return item.locale
|
|
189
|
+
?? item.localeCode
|
|
190
|
+
?? item.i18n?.locale?.code
|
|
191
|
+
?? item.i18n?.localeCode
|
|
192
|
+
?? null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function extractWebinyTenant(item) {
|
|
196
|
+
return item.tenant
|
|
197
|
+
?? item.tenantId
|
|
198
|
+
?? item.createdBy?.tenant
|
|
199
|
+
?? null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function normalizeContentItem(item) {
|
|
203
|
+
const model = item.model
|
|
204
|
+
?? item.modelId
|
|
205
|
+
?? item.__typename
|
|
206
|
+
?? item.contentModel?.modelId
|
|
207
|
+
?? null;
|
|
208
|
+
return {
|
|
209
|
+
id: item.id,
|
|
210
|
+
contentId: item.contentId ?? item.contentid ?? item.entryId ?? item.id,
|
|
211
|
+
model,
|
|
212
|
+
locale: extractWebinyLocale(item) ?? undefined,
|
|
213
|
+
tenant: extractWebinyTenant(item) ?? undefined,
|
|
214
|
+
values: normalizeValues(item),
|
|
215
|
+
createdAt: item.createdAt ?? item.createdOn,
|
|
216
|
+
updatedAt: item.updatedAt ?? item.savedOn ?? item.publishedOn,
|
|
217
|
+
version: item._version ?? item.version,
|
|
218
|
+
lastChangedAt: item._lastChangedAt ?? item.lastChangedAt
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function matchesConfiguredTenant(item, configuredTenant) {
|
|
223
|
+
if (!configuredTenant) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const tenant = item.tenant ?? item.tenantId ?? item.createdBy?.tenant ?? null;
|
|
228
|
+
return tenant != null && String(tenant) === String(configuredTenant);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function handler(event) {
|
|
232
|
+
const clients = createAwsClients();
|
|
233
|
+
const tableName = process.env.S3TE_CONTENT_TABLE;
|
|
234
|
+
const renderWorkerName = process.env.S3TE_RENDER_WORKER_NAME;
|
|
235
|
+
const environmentName = process.env.S3TE_ENVIRONMENT;
|
|
236
|
+
const configuredTenant = String(process.env.S3TE_WEBINY_TENANT ?? "").trim();
|
|
237
|
+
const relevantModels = new Set(String(process.env.S3TE_RELEVANT_MODELS ?? "")
|
|
238
|
+
.split(",")
|
|
239
|
+
.map((entry) => entry.trim())
|
|
240
|
+
.filter(Boolean));
|
|
241
|
+
|
|
242
|
+
let mirrored = 0;
|
|
243
|
+
let deleted = 0;
|
|
244
|
+
|
|
245
|
+
for (const record of event.Records ?? []) {
|
|
246
|
+
const image = record.dynamodb?.NewImage ?? record.dynamodb?.OldImage;
|
|
247
|
+
if (!image) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const item = clients.AWS.DynamoDB.Converter.unmarshall(image);
|
|
252
|
+
if (!matchesConfiguredTenant(item, configuredTenant)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const contentItem = normalizeContentItem(item);
|
|
257
|
+
if (!contentItem.id || !contentItem.model || (relevantModels.size > 0 && !relevantModels.has(contentItem.model))) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const shouldDelete = record.eventName === "REMOVE" || !isPublished(item);
|
|
262
|
+
if (shouldDelete) {
|
|
263
|
+
await clients.dynamo.delete({
|
|
264
|
+
TableName: tableName,
|
|
265
|
+
Key: {
|
|
266
|
+
id: contentItem.id
|
|
267
|
+
}
|
|
268
|
+
}).promise();
|
|
269
|
+
deleted += 1;
|
|
270
|
+
await invokeLambdaEvent(clients.lambda, renderWorkerName, {
|
|
271
|
+
type: "content-item",
|
|
272
|
+
action: "delete",
|
|
273
|
+
environment: environmentName,
|
|
274
|
+
contentId: contentItem.contentId,
|
|
275
|
+
model: contentItem.model,
|
|
276
|
+
buildId: `content-${Date.now()}`
|
|
277
|
+
});
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await clients.dynamo.put({
|
|
282
|
+
TableName: tableName,
|
|
283
|
+
Item: contentItem
|
|
284
|
+
}).promise();
|
|
285
|
+
mirrored += 1;
|
|
286
|
+
await invokeLambdaEvent(clients.lambda, renderWorkerName, {
|
|
287
|
+
type: "content-item",
|
|
288
|
+
action: "upsert",
|
|
289
|
+
environment: environmentName,
|
|
290
|
+
contentId: contentItem.contentId,
|
|
291
|
+
model: contentItem.model,
|
|
292
|
+
item: contentItem,
|
|
293
|
+
buildId: `content-${Date.now()}`
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
mirrored,
|
|
299
|
+
deleted
|
|
300
|
+
};
|
|
301
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createAwsClients } from "./common.mjs";
|
|
2
|
+
|
|
3
|
+
function chunk(items, size) {
|
|
4
|
+
const chunks = [];
|
|
5
|
+
for (let index = 0; index < items.length; index += size) {
|
|
6
|
+
chunks.push(items.slice(index, index + size));
|
|
7
|
+
}
|
|
8
|
+
return chunks;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function handler(event) {
|
|
12
|
+
const clients = createAwsClients();
|
|
13
|
+
const tableName = process.env.S3TE_INVALIDATION_TABLE;
|
|
14
|
+
const distributionId = event.distributionId;
|
|
15
|
+
|
|
16
|
+
const response = await clients.dynamo.query({
|
|
17
|
+
TableName: tableName,
|
|
18
|
+
KeyConditionExpression: "distributionId = :distributionId",
|
|
19
|
+
ExpressionAttributeValues: {
|
|
20
|
+
":distributionId": distributionId
|
|
21
|
+
}
|
|
22
|
+
}).promise();
|
|
23
|
+
|
|
24
|
+
const items = response.Items ?? [];
|
|
25
|
+
const requests = items.filter((item) => item.requestId !== "__window__" && item.type === "request");
|
|
26
|
+
if (requests.length > 0) {
|
|
27
|
+
await clients.cloudFront.createInvalidation({
|
|
28
|
+
DistributionId: distributionId,
|
|
29
|
+
InvalidationBatch: {
|
|
30
|
+
CallerReference: `${distributionId}-${Date.now()}`,
|
|
31
|
+
Paths: {
|
|
32
|
+
Quantity: 1,
|
|
33
|
+
Items: ["/*"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}).promise();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const deletions = items.map((item) => ({
|
|
40
|
+
DeleteRequest: {
|
|
41
|
+
Key: {
|
|
42
|
+
distributionId: item.distributionId,
|
|
43
|
+
requestId: item.requestId
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
for (const batch of chunk(deletions, 25)) {
|
|
49
|
+
await clients.dynamo.batchWrite({
|
|
50
|
+
RequestItems: {
|
|
51
|
+
[tableName]: batch
|
|
52
|
+
}
|
|
53
|
+
}).promise();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
distributionId,
|
|
58
|
+
invalidated: requests.length > 0,
|
|
59
|
+
deletedRecords: deletions.length
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { createAwsClients } from "./common.mjs";
|
|
2
|
+
|
|
3
|
+
export async function handler(event) {
|
|
4
|
+
const clients = createAwsClients();
|
|
5
|
+
const tableName = process.env.S3TE_INVALIDATION_TABLE;
|
|
6
|
+
const stateMachineArn = process.env.S3TE_INVALIDATION_STATE_MACHINE_ARN;
|
|
7
|
+
|
|
8
|
+
const requestId = `${event.requestedAt ?? new Date().toISOString()}#${event.buildId ?? "build"}`;
|
|
9
|
+
await clients.dynamo.put({
|
|
10
|
+
TableName: tableName,
|
|
11
|
+
Item: {
|
|
12
|
+
distributionId: event.distributionId,
|
|
13
|
+
requestId,
|
|
14
|
+
type: "request",
|
|
15
|
+
environment: event.environment,
|
|
16
|
+
variant: event.variant,
|
|
17
|
+
language: event.language,
|
|
18
|
+
distributionAliases: event.distributionAliases ?? [],
|
|
19
|
+
paths: event.paths ?? ["/*"],
|
|
20
|
+
requestedAt: event.requestedAt ?? new Date().toISOString(),
|
|
21
|
+
status: "pending"
|
|
22
|
+
}
|
|
23
|
+
}).promise();
|
|
24
|
+
|
|
25
|
+
let windowOpened = false;
|
|
26
|
+
try {
|
|
27
|
+
await clients.dynamo.put({
|
|
28
|
+
TableName: tableName,
|
|
29
|
+
Item: {
|
|
30
|
+
distributionId: event.distributionId,
|
|
31
|
+
requestId: "__window__",
|
|
32
|
+
type: "window",
|
|
33
|
+
windowOpenedAt: new Date().toISOString()
|
|
34
|
+
},
|
|
35
|
+
ConditionExpression: "attribute_not_exists(distributionId)"
|
|
36
|
+
}).promise();
|
|
37
|
+
windowOpened = true;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const errorCode = error?.name ?? error?.Code ?? error?.code;
|
|
40
|
+
if (errorCode !== "ConditionalCheckFailedException") {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (windowOpened) {
|
|
46
|
+
await clients.stepFunctions.startExecution({
|
|
47
|
+
stateMachineArn,
|
|
48
|
+
input: JSON.stringify({
|
|
49
|
+
distributionId: event.distributionId
|
|
50
|
+
})
|
|
51
|
+
}).promise();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
distributionId: event.distributionId,
|
|
56
|
+
requestId,
|
|
57
|
+
windowOpened
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAwsClients,
|
|
3
|
+
createBuildId,
|
|
4
|
+
createRepositoriesAndPublishers,
|
|
5
|
+
deleteOutputsForTemplate,
|
|
6
|
+
loadEnvironmentManifest,
|
|
7
|
+
renderAndPublishTargets,
|
|
8
|
+
resolveRenderTargetsForEvent,
|
|
9
|
+
S3TemplateRepository
|
|
10
|
+
} from "./common.mjs";
|
|
11
|
+
|
|
12
|
+
export async function handler(event) {
|
|
13
|
+
const environmentName = process.env.S3TE_ENVIRONMENT;
|
|
14
|
+
const runtimeParameter = process.env.S3TE_RUNTIME_PARAMETER;
|
|
15
|
+
const clients = createAwsClients();
|
|
16
|
+
const { manifest, environment: environmentManifest } = await loadEnvironmentManifest(
|
|
17
|
+
clients.ssm,
|
|
18
|
+
runtimeParameter,
|
|
19
|
+
environmentName
|
|
20
|
+
);
|
|
21
|
+
const {
|
|
22
|
+
contentRepository,
|
|
23
|
+
dependencyStore,
|
|
24
|
+
publisher,
|
|
25
|
+
invalidationScheduler
|
|
26
|
+
} = createRepositoriesAndPublishers({
|
|
27
|
+
clients,
|
|
28
|
+
environmentManifest
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const buildId = event.buildId ?? createBuildId("render");
|
|
32
|
+
|
|
33
|
+
if (event.type === "source-object" && event.action === "delete" && event.key.startsWith(`${event.variant}/`)) {
|
|
34
|
+
const deletedOutputs = await deleteOutputsForTemplate({
|
|
35
|
+
event,
|
|
36
|
+
environmentName,
|
|
37
|
+
environmentManifest,
|
|
38
|
+
dependencyStore,
|
|
39
|
+
publisher,
|
|
40
|
+
invalidationScheduler,
|
|
41
|
+
buildId
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
buildId,
|
|
46
|
+
rendered: [],
|
|
47
|
+
deleted: deletedOutputs,
|
|
48
|
+
warnings: []
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const targets = await resolveRenderTargetsForEvent({
|
|
53
|
+
event,
|
|
54
|
+
manifest,
|
|
55
|
+
environmentName,
|
|
56
|
+
environmentManifest,
|
|
57
|
+
dependencyStore,
|
|
58
|
+
templateRepositoryFactory: (variantName) => new S3TemplateRepository({
|
|
59
|
+
s3: clients.s3,
|
|
60
|
+
environmentManifest,
|
|
61
|
+
activeVariantName: variantName
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const result = await renderAndPublishTargets({
|
|
66
|
+
manifest,
|
|
67
|
+
environmentName,
|
|
68
|
+
environmentManifest,
|
|
69
|
+
contentRepository,
|
|
70
|
+
dependencyStore,
|
|
71
|
+
publisher,
|
|
72
|
+
invalidationScheduler,
|
|
73
|
+
targets,
|
|
74
|
+
buildId
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
buildId,
|
|
79
|
+
rendered: result.rendered,
|
|
80
|
+
deleted: result.deleted,
|
|
81
|
+
warnings: result.warnings
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildInvalidationRequest,
|
|
3
|
+
createAwsClients,
|
|
4
|
+
createBuildId,
|
|
5
|
+
decodeS3Key,
|
|
6
|
+
invokeLambdaEvent,
|
|
7
|
+
isRenderableBucketKey,
|
|
8
|
+
loadEnvironmentManifest
|
|
9
|
+
} from "./common.mjs";
|
|
10
|
+
|
|
11
|
+
function variantFromBucket(environmentManifest, bucketName) {
|
|
12
|
+
return Object.keys(environmentManifest.variants).find((variantName) => (
|
|
13
|
+
environmentManifest.variants[variantName].codeBucket === bucketName
|
|
14
|
+
));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function outputKeyFromAssetKey(variantName, key) {
|
|
18
|
+
if (key.startsWith(`${variantName}/`)) {
|
|
19
|
+
return key.slice(variantName.length + 1);
|
|
20
|
+
}
|
|
21
|
+
return key;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function handler(event) {
|
|
25
|
+
const environmentName = process.env.S3TE_ENVIRONMENT;
|
|
26
|
+
const runtimeParameter = process.env.S3TE_RUNTIME_PARAMETER;
|
|
27
|
+
const renderWorkerName = process.env.S3TE_RENDER_WORKER_NAME;
|
|
28
|
+
const renderExtensions = String(process.env.S3TE_RENDER_EXTENSIONS ?? "")
|
|
29
|
+
.split(",")
|
|
30
|
+
.map((entry) => entry.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
|
|
33
|
+
const clients = createAwsClients();
|
|
34
|
+
const { environment: environmentManifest } = await loadEnvironmentManifest(
|
|
35
|
+
clients.ssm,
|
|
36
|
+
runtimeParameter,
|
|
37
|
+
environmentName
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
let copiedAssets = 0;
|
|
41
|
+
let deletedAssets = 0;
|
|
42
|
+
let dispatchedBuilds = 0;
|
|
43
|
+
|
|
44
|
+
for (const record of event.Records ?? []) {
|
|
45
|
+
const bucketName = record.s3?.bucket?.name;
|
|
46
|
+
const key = decodeS3Key(record.s3?.object?.key ?? "");
|
|
47
|
+
const variantName = variantFromBucket(environmentManifest, bucketName);
|
|
48
|
+
if (!variantName || !key) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const action = String(record.eventName).startsWith("ObjectRemoved:") ? "delete" : "upsert";
|
|
53
|
+
const buildId = createBuildId("source");
|
|
54
|
+
|
|
55
|
+
if (isRenderableBucketKey(environmentManifest, variantName, key, renderExtensions)) {
|
|
56
|
+
await invokeLambdaEvent(clients.lambda, renderWorkerName, {
|
|
57
|
+
type: "source-object",
|
|
58
|
+
action,
|
|
59
|
+
bucket: bucketName,
|
|
60
|
+
key,
|
|
61
|
+
environment: environmentName,
|
|
62
|
+
variant: variantName,
|
|
63
|
+
buildId
|
|
64
|
+
});
|
|
65
|
+
dispatchedBuilds += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const outputKey = outputKeyFromAssetKey(variantName, key);
|
|
70
|
+
for (const languageCode of Object.keys(environmentManifest.variants[variantName].languages)) {
|
|
71
|
+
const targetBucket = environmentManifest.variants[variantName].languages[languageCode].targetBucket;
|
|
72
|
+
if (action === "upsert") {
|
|
73
|
+
await clients.s3.copyObject({
|
|
74
|
+
Bucket: targetBucket,
|
|
75
|
+
Key: outputKey,
|
|
76
|
+
CopySource: `${bucketName}/${key.split("/").map(encodeURIComponent).join("/")}`
|
|
77
|
+
}).promise();
|
|
78
|
+
copiedAssets += 1;
|
|
79
|
+
} else {
|
|
80
|
+
await clients.s3.deleteObject({
|
|
81
|
+
Bucket: targetBucket,
|
|
82
|
+
Key: outputKey
|
|
83
|
+
}).promise();
|
|
84
|
+
deletedAssets += 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await invokeLambdaEvent(
|
|
88
|
+
clients.lambda,
|
|
89
|
+
environmentManifest.functions.invalidationScheduler,
|
|
90
|
+
buildInvalidationRequest(
|
|
91
|
+
environmentName,
|
|
92
|
+
environmentManifest,
|
|
93
|
+
variantName,
|
|
94
|
+
languageCode,
|
|
95
|
+
buildId
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
copiedAssets,
|
|
103
|
+
deletedAssets,
|
|
104
|
+
dispatchedBuilds
|
|
105
|
+
};
|
|
106
|
+
}
|