cdk-nuxt 1.1.0 → 1.2.3
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/README.md +6 -1
- package/lib/functions/assets-cleanup/build/app/index.js +94 -60
- package/lib/functions/assets-cleanup/build/app/index.js.map +1 -1
- package/lib/functions/assets-cleanup/index.js +95 -63
- package/lib/functions/assets-cleanup/index.ts +138 -70
- package/lib/stack/nuxt-app-static-assets.js +39 -13
- package/lib/stack/nuxt-app-static-assets.ts +40 -13
- package/lib/stack/server/nuxt-server-app-stack.d.ts +17 -5
- package/lib/stack/server/nuxt-server-app-stack.js +28 -8
- package/lib/stack/server/nuxt-server-app-stack.ts +40 -10
- package/package.json +2 -2
- package/lib/.DS_Store +0 -0
- package/lib/functions/.DS_Store +0 -0
- package/lib/functions/assets-cleanup/.DS_Store +0 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CDK Nuxt 3
|
|
1
|
+
# AWS CDK Nuxt 3 Deployment Stack
|
|
2
2
|
|
|
3
3
|
<p>
|
|
4
4
|
<a href="https://github.com/ferdinandfrank/cdk-nuxt/actions/workflows/publish.yml"><img alt="Build" src="https://img.shields.io/github/actions/workflow/status/ferdinandfrank/cdk-nuxt/publish.yml?logo=github" /></a>
|
|
@@ -116,6 +116,11 @@ Whether to enable AWS X-Ray for the Nuxt Lambda function.
|
|
|
116
116
|
### enableSitemap?: boolean
|
|
117
117
|
Whether to enable a global Sitemap bucket which is permanently accessible through multiple deployments.
|
|
118
118
|
|
|
119
|
+
### outdatedAssetsRetentionDays?: boolean
|
|
120
|
+
The number of days to retain static assets of outdated deployments in the S3 bucket.
|
|
121
|
+
Useful to allow users to still access old assets after a new deployment when they are still browsing on an old version.
|
|
122
|
+
Defaults to 30 days.
|
|
123
|
+
|
|
119
124
|
### allowHeaders?: string[]
|
|
120
125
|
An array of headers to pass to the Nuxt app on SSR requests.
|
|
121
126
|
The more headers are passed, the weaker the cache performance will be, as the cache key
|
|
@@ -1,7 +1,51 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
4
|
-
const
|
|
4
|
+
const consumers_1 = require("stream/consumers");
|
|
5
|
+
const MAX_DELETE_OBJECT_KEYS = 1000;
|
|
6
|
+
const ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the current deployment revision (build timestamp).
|
|
9
|
+
*/
|
|
10
|
+
const getCurrentRevision = async (s3Client, bucketName) => {
|
|
11
|
+
const revisionFile = await s3Client.send(new client_s3_1.GetObjectCommand({
|
|
12
|
+
Bucket: bucketName,
|
|
13
|
+
Key: 'app-revision',
|
|
14
|
+
}));
|
|
15
|
+
return new Date(await (0, consumers_1.text)(revisionFile.Body));
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Filters the given assets by inspecting their revision and returns those, that are older than the specified cutoff date.
|
|
19
|
+
*/
|
|
20
|
+
const filterOutdatedAssetKeys = (metadataResults, returnOlderThan) => {
|
|
21
|
+
return metadataResults
|
|
22
|
+
.filter(assetMetadata => assetMetadata.metadata.revision
|
|
23
|
+
? new Date(assetMetadata.metadata.revision).getTime() <= returnOlderThan.getTime()
|
|
24
|
+
: false)
|
|
25
|
+
.map(filteredAssets => filteredAssets.key);
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Deletes the given assets.
|
|
29
|
+
*/
|
|
30
|
+
const deleteAssets = async (assetKeys, s3Client, bucketName) => {
|
|
31
|
+
let remainingAssetKeysToDelete = [...assetKeys];
|
|
32
|
+
const pendingDeletes = [];
|
|
33
|
+
while (remainingAssetKeysToDelete.length > 0) {
|
|
34
|
+
const curDeleteBatch = remainingAssetKeysToDelete.slice(0, MAX_DELETE_OBJECT_KEYS);
|
|
35
|
+
remainingAssetKeysToDelete = remainingAssetKeysToDelete.slice(MAX_DELETE_OBJECT_KEYS);
|
|
36
|
+
console.log('Deleting assets:', curDeleteBatch);
|
|
37
|
+
const pendingDelete = s3Client.send(new client_s3_1.DeleteObjectsCommand({
|
|
38
|
+
Bucket: bucketName,
|
|
39
|
+
Delete: {
|
|
40
|
+
Objects: curDeleteBatch.map(outdatedKey => {
|
|
41
|
+
return { Key: outdatedKey };
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
pendingDeletes.push(pendingDelete);
|
|
46
|
+
}
|
|
47
|
+
return await Promise.all(pendingDeletes);
|
|
48
|
+
};
|
|
5
49
|
exports.handler = async (event, context) => {
|
|
6
50
|
console.log('Starting cleanup of static assets older than 1 week...');
|
|
7
51
|
try {
|
|
@@ -10,68 +54,58 @@ exports.handler = async (event, context) => {
|
|
|
10
54
|
if (!bucketName) {
|
|
11
55
|
throw new Error("Static asset's bucket name not specified in environment!");
|
|
12
56
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const deploymentFolders = await client.send(new client_s3_1.ListObjectsV2Command({
|
|
16
|
-
Bucket: bucketName,
|
|
17
|
-
Delimiter: '/', // Only retrieve the top level folders
|
|
18
|
-
}));
|
|
19
|
-
// The result is a list of objects containing the keys with a trailing slash
|
|
20
|
-
const folderNames = deploymentFolders.CommonPrefixes?.map(folder => folder.Prefix?.slice(0, -1));
|
|
21
|
-
if (!folderNames) {
|
|
22
|
-
console.log('Canceled static assets cleanup as folder is empty.');
|
|
23
|
-
return;
|
|
57
|
+
if (!process.env.OUTDATED_ASSETS_RETENTION_DAYS) {
|
|
58
|
+
throw new Error('Retain duration of static assets not specified!');
|
|
24
59
|
}
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
console.log('No outdated asset folders found.');
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
console.log(`Deleting ${outdatedFolderNames.length} outdated folders...`);
|
|
46
|
-
// Unfortunately it's not possible to delete folders recursively 🙄
|
|
47
|
-
// so we need to query all the contents in order to delete them
|
|
48
|
-
const pendingPromises = outdatedFolderNames.map(folderName => {
|
|
49
|
-
return client
|
|
50
|
-
.send(new client_s3_1.ListObjectsV2Command({
|
|
60
|
+
const retainAssetsInDays = Number.parseInt(process.env.OUTDATED_ASSETS_RETENTION_DAYS);
|
|
61
|
+
const currentRevision = await getCurrentRevision(client, bucketName);
|
|
62
|
+
const deleteOlderThan = new Date(currentRevision.getTime() - retainAssetsInDays * ONE_DAY_IN_MILLISECONDS);
|
|
63
|
+
let assetKeysToDelete = [];
|
|
64
|
+
let lastToken = undefined;
|
|
65
|
+
do {
|
|
66
|
+
const curAssetsResult = await client.send(new client_s3_1.ListObjectsV2Command({
|
|
67
|
+
Bucket: bucketName,
|
|
68
|
+
MaxKeys: 250,
|
|
69
|
+
ContinuationToken: lastToken,
|
|
70
|
+
}));
|
|
71
|
+
// Read object metadata in blocks of 10
|
|
72
|
+
let processableAssets = [...curAssetsResult.Contents];
|
|
73
|
+
while (processableAssets.length > 0) {
|
|
74
|
+
const assetsBatch = processableAssets.slice(0, 10);
|
|
75
|
+
processableAssets = processableAssets.slice(10);
|
|
76
|
+
const pendingMetadataRequests = assetsBatch.map(asset => client.send(new client_s3_1.HeadObjectCommand({
|
|
51
77
|
Bucket: bucketName,
|
|
52
|
-
|
|
53
|
-
}))
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
Key: asset.Key,
|
|
79
|
+
})));
|
|
80
|
+
const metadataResults = await Promise.all(pendingMetadataRequests);
|
|
81
|
+
// Assign metadata to assets
|
|
82
|
+
const metadataByAsset = metadataResults.map((metadataResult, index) => ({
|
|
83
|
+
key: assetsBatch[index].Key,
|
|
84
|
+
metadata: metadataResult.Metadata,
|
|
85
|
+
}));
|
|
86
|
+
const outdatedAssetKeys = filterOutdatedAssetKeys(metadataByAsset, deleteOlderThan);
|
|
87
|
+
assetKeysToDelete.push(...outdatedAssetKeys);
|
|
88
|
+
}
|
|
89
|
+
lastToken = curAssetsResult.NextContinuationToken;
|
|
90
|
+
} while (lastToken !== undefined);
|
|
91
|
+
if (assetKeysToDelete.length === 0) {
|
|
92
|
+
console.log('No outdated assets to delete found');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log('Deleting ' + assetKeysToDelete.length + ' assets...');
|
|
96
|
+
// Delete outdated assets (max. 1000 allowed per request)
|
|
97
|
+
const results = await deleteAssets(assetKeysToDelete, client, bucketName);
|
|
98
|
+
const failed = results.reduce((previousResult, currentResult) => {
|
|
99
|
+
const currentError = !!(currentResult.Errors && currentResult.Errors.length > 0);
|
|
100
|
+
if (currentError) {
|
|
101
|
+
console.error('Failed to delete outdated static assets', currentResult.Errors);
|
|
102
|
+
}
|
|
103
|
+
return previousResult || currentError;
|
|
104
|
+
}, false);
|
|
105
|
+
if (failed) {
|
|
106
|
+
throw new Error('Failed to delete outdated static assets');
|
|
73
107
|
}
|
|
74
|
-
console.log('Cleanup of old static assets finished
|
|
108
|
+
console.log('Cleanup of old static assets finished');
|
|
75
109
|
}
|
|
76
110
|
catch (error) {
|
|
77
111
|
console.error('### unexpected runtime error ###', error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import {DeleteObjectsCommand, ListObjectsV2Command, S3Client} from \"@aws-sdk/client-s3\";\n\nconst ONE_WEEK_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 7;\n\nexports.handler = async (event: any, context: any) => {\n console.log('Starting cleanup of static assets older than 1 week...');\n\n try {\n const client = new S3Client({region: process.env.AWS_REGION});\n const bucketName = process.env.STATIC_ASSETS_BUCKET;\n\n if (!bucketName) {\n throw new Error(\"Static asset's bucket name not specified in environment!\");\n }\n\n const deleteOlderThan = new Date(Date.now() - ONE_WEEK_IN_MILLISECONDS);\n\n // As we don't have hundreds of deployments per week, there's no need for pagination\n const deploymentFolders = await client.send(\n new ListObjectsV2Command({\n Bucket: bucketName,\n Delimiter: '/', // Only retrieve the top level folders\n })\n );\n\n // The result is a list of objects containing the keys with a trailing slash\n const folderNames = deploymentFolders.CommonPrefixes?.map(folder => folder.Prefix?.slice(0, -1))\n if (!folderNames) {\n console.log('Canceled static assets cleanup as folder is empty.');\n return;\n }\n\n // We sort the folders by their creation data and remove the latest one as this is the currently active one used in production\n const legacyFolderNames = folderNames.sort((a,b) => {\n // @ts-ignore\n const creationDateA = new Date(a);\n // @ts-ignore\n const creationDateB = new Date(b);\n\n return creationDateA.getTime() < creationDateB.getTime() ? -1 : 1;\n })\n const activeFolderName = legacyFolderNames.pop();\n console.log(`Detected assets folder \"${activeFolderName}\" as current production folder...`);\n\n // We want to get all outdated folders of our legacy (not productively used) folders for deletion\n const outdatedFolderNames = legacyFolderNames.filter(folderName => {\n // @ts-ignore\n const creationDate = new Date(folderName);\n return creationDate.getTime() < deleteOlderThan.getTime();\n }\n );\n\n if (!outdatedFolderNames || outdatedFolderNames.length === 0) {\n console.log('No outdated asset folders found.');\n } else {\n console.log(`Deleting ${outdatedFolderNames.length} outdated folders...`);\n\n // Unfortunately it's not possible to delete folders recursively 🙄\n // so we need to query all the contents in order to delete them\n const pendingPromises = outdatedFolderNames.map(folderName => {\n return client\n .send(\n new ListObjectsV2Command({\n Bucket: bucketName,\n Prefix: folderName,\n })\n )\n .then(outdatedAssets => {\n return client.send(\n new DeleteObjectsCommand({\n Bucket: bucketName,\n Delete: {\n Objects: outdatedAssets.Contents?.map(outdatedAsset => {\n return {Key: outdatedAsset.Key};\n }),\n },\n })\n );\n });\n });\n\n const results = await Promise.all(pendingPromises);\n results.forEach(result => {\n if (result.Errors && result.Errors.length) {\n const errorMsg = 'Failed to delete outdated static assets.';\n console.error(errorMsg, result.Errors);\n throw new Error(errorMsg);\n }\n });\n }\n\n console.log('Cleanup of old static assets finished.');\n } catch (error) {\n console.error('### unexpected runtime error ###', error);\n }\n};"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n DeleteObjectsCommand,\n DeleteObjectsCommandOutput,\n GetObjectCommand, HeadObjectCommand,\n ListObjectsV2Command,\n S3Client\n} from \"@aws-sdk/client-s3\";\nimport type {ListObjectsV2CommandOutput} from \"@aws-sdk/client-s3\";\nimport {text} from 'stream/consumers';\nimport {Readable} from \"stream\";\n\ninterface AssetMetadata {\n readonly key: string;\n readonly metadata: {[key: string]: string};\n}\n\nconst MAX_DELETE_OBJECT_KEYS = 1000;\nconst ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;\n\n/**\n * Returns the current deployment revision (build timestamp).\n */\nconst getCurrentRevision = async (s3Client: S3Client, bucketName: string): Promise<Date> => {\n const revisionFile = await s3Client.send(\n new GetObjectCommand({\n Bucket: bucketName,\n Key: 'app-revision',\n })\n );\n\n return new Date(await text(revisionFile.Body as Readable));\n};\n\n/**\n * Filters the given assets by inspecting their revision and returns those, that are older than the specified cutoff date.\n */\nconst filterOutdatedAssetKeys = (metadataResults: AssetMetadata[], returnOlderThan: Date): string[] => {\n return metadataResults\n .filter(assetMetadata =>\n assetMetadata.metadata.revision\n ? new Date(assetMetadata.metadata.revision).getTime() <= returnOlderThan.getTime()\n : false\n )\n .map(filteredAssets => filteredAssets.key);\n};\n\n/**\n * Deletes the given assets.\n */\nconst deleteAssets = async (\n assetKeys: string[],\n s3Client: S3Client,\n bucketName: string\n): Promise<DeleteObjectsCommandOutput[]> => {\n let remainingAssetKeysToDelete = [...assetKeys];\n const pendingDeletes = [];\n\n while (remainingAssetKeysToDelete.length > 0) {\n const curDeleteBatch = remainingAssetKeysToDelete.slice(0, MAX_DELETE_OBJECT_KEYS);\n remainingAssetKeysToDelete = remainingAssetKeysToDelete.slice(MAX_DELETE_OBJECT_KEYS);\n\n console.log('Deleting assets:', curDeleteBatch);\n\n const pendingDelete = s3Client.send(\n new DeleteObjectsCommand({\n Bucket: bucketName,\n Delete: {\n Objects: curDeleteBatch.map(outdatedKey => {\n return {Key: outdatedKey};\n }),\n },\n })\n );\n pendingDeletes.push(pendingDelete);\n }\n\n return await Promise.all(pendingDeletes);\n};\n\nexports.handler = async (event: any, context: any) => {\n console.log('Starting cleanup of static assets older than 1 week...');\n\n try {\n const client = new S3Client({region: process.env.AWS_REGION});\n const bucketName = process.env.STATIC_ASSETS_BUCKET;\n if (!bucketName) {\n throw new Error(\"Static asset's bucket name not specified in environment!\");\n }\n\n if (!process.env.OUTDATED_ASSETS_RETENTION_DAYS) {\n throw new Error('Retain duration of static assets not specified!');\n }\n const retainAssetsInDays = Number.parseInt(process.env.OUTDATED_ASSETS_RETENTION_DAYS);\n const currentRevision = await getCurrentRevision(client, bucketName);\n const deleteOlderThan = new Date(currentRevision.getTime() - retainAssetsInDays * ONE_DAY_IN_MILLISECONDS);\n\n let assetKeysToDelete: string[] = [];\n let lastToken = undefined;\n\n do {\n const curAssetsResult: ListObjectsV2CommandOutput = await client.send(\n new ListObjectsV2Command({\n Bucket: bucketName,\n MaxKeys: 250,\n ContinuationToken: lastToken,\n })\n );\n\n // Read object metadata in blocks of 10\n let processableAssets = [...curAssetsResult.Contents!];\n\n while (processableAssets.length > 0) {\n const assetsBatch = processableAssets.slice(0, 10);\n processableAssets = processableAssets.slice(10);\n\n const pendingMetadataRequests = assetsBatch.map(asset =>\n client.send(\n new HeadObjectCommand({\n Bucket: bucketName,\n Key: asset.Key,\n })\n )\n );\n\n const metadataResults = await Promise.all(pendingMetadataRequests);\n\n // Assign metadata to assets\n const metadataByAsset: AssetMetadata[] = (metadataResults.map((metadataResult, index) => ({\n key: assetsBatch[index].Key,\n metadata: metadataResult.Metadata,\n })) as AssetMetadata[]);\n\n const outdatedAssetKeys = filterOutdatedAssetKeys(metadataByAsset, deleteOlderThan);\n assetKeysToDelete.push(...outdatedAssetKeys);\n }\n lastToken = curAssetsResult.NextContinuationToken;\n } while (lastToken !== undefined);\n\n if (assetKeysToDelete.length === 0) {\n console.log('No outdated assets to delete found');\n return;\n }\n\n console.log('Deleting ' + assetKeysToDelete.length + ' assets...');\n\n // Delete outdated assets (max. 1000 allowed per request)\n const results = await deleteAssets(assetKeysToDelete, client, bucketName);\n const failed = results.reduce((previousResult: boolean, currentResult: DeleteObjectsCommandOutput): boolean => {\n const currentError: boolean = !!(currentResult.Errors && currentResult.Errors.length > 0);\n if (currentError) {\n console.error('Failed to delete outdated static assets', currentResult.Errors);\n }\n return previousResult || currentError;\n }, false);\n\n if (failed) {\n throw new Error('Failed to delete outdated static assets');\n }\n\n console.log('Cleanup of old static assets finished');\n } catch (error) {\n console.error('### unexpected runtime error ###', error);\n }\n};"]}
|
|
@@ -1,9 +1,52 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
4
|
-
const
|
|
4
|
+
const consumers_1 = require("stream/consumers");
|
|
5
|
+
const MAX_DELETE_OBJECT_KEYS = 1000;
|
|
6
|
+
const ONE_DAY_IN_MILLISECONDS = 1000 * 60 * 60 * 24;
|
|
7
|
+
/**
|
|
8
|
+
* Returns the current deployment revision (build timestamp).
|
|
9
|
+
*/
|
|
10
|
+
const getCurrentRevision = async (s3Client, bucketName) => {
|
|
11
|
+
const revisionFile = await s3Client.send(new client_s3_1.GetObjectCommand({
|
|
12
|
+
Bucket: bucketName,
|
|
13
|
+
Key: 'app-revision',
|
|
14
|
+
}));
|
|
15
|
+
return new Date(await (0, consumers_1.text)(revisionFile.Body));
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Filters the given assets by inspecting their revision and returns those, that are older than the specified cutoff date.
|
|
19
|
+
*/
|
|
20
|
+
const filterOutdatedAssetKeys = (metadataResults, returnOlderThan) => {
|
|
21
|
+
return metadataResults
|
|
22
|
+
.filter(assetMetadata => assetMetadata.metadata.revision
|
|
23
|
+
? new Date(assetMetadata.metadata.revision).getTime() <= returnOlderThan.getTime()
|
|
24
|
+
: false)
|
|
25
|
+
.map(filteredAssets => filteredAssets.key);
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Deletes the given assets.
|
|
29
|
+
*/
|
|
30
|
+
const deleteAssets = async (assetKeys, s3Client, bucketName) => {
|
|
31
|
+
let remainingAssetKeysToDelete = [...assetKeys];
|
|
32
|
+
const pendingDeletes = [];
|
|
33
|
+
while (remainingAssetKeysToDelete.length > 0) {
|
|
34
|
+
const curDeleteBatch = remainingAssetKeysToDelete.slice(0, MAX_DELETE_OBJECT_KEYS);
|
|
35
|
+
remainingAssetKeysToDelete = remainingAssetKeysToDelete.slice(MAX_DELETE_OBJECT_KEYS);
|
|
36
|
+
console.log('Deleting assets:', curDeleteBatch);
|
|
37
|
+
const pendingDelete = s3Client.send(new client_s3_1.DeleteObjectsCommand({
|
|
38
|
+
Bucket: bucketName,
|
|
39
|
+
Delete: {
|
|
40
|
+
Objects: curDeleteBatch.map(outdatedKey => {
|
|
41
|
+
return { Key: outdatedKey };
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
pendingDeletes.push(pendingDelete);
|
|
46
|
+
}
|
|
47
|
+
return await Promise.all(pendingDeletes);
|
|
48
|
+
};
|
|
5
49
|
exports.handler = async (event, context) => {
|
|
6
|
-
var _a;
|
|
7
50
|
console.log('Starting cleanup of static assets older than 1 week...');
|
|
8
51
|
try {
|
|
9
52
|
const client = new client_s3_1.S3Client({ region: process.env.AWS_REGION });
|
|
@@ -11,72 +54,61 @@ exports.handler = async (event, context) => {
|
|
|
11
54
|
if (!bucketName) {
|
|
12
55
|
throw new Error("Static asset's bucket name not specified in environment!");
|
|
13
56
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const deploymentFolders = await client.send(new client_s3_1.ListObjectsV2Command({
|
|
17
|
-
Bucket: bucketName,
|
|
18
|
-
Delimiter: '/', // Only retrieve the top level folders
|
|
19
|
-
}));
|
|
20
|
-
// The result is a list of objects containing the keys with a trailing slash
|
|
21
|
-
const folderNames = (_a = deploymentFolders.CommonPrefixes) === null || _a === void 0 ? void 0 : _a.map(folder => { var _a; return (_a = folder.Prefix) === null || _a === void 0 ? void 0 : _a.slice(0, -1); });
|
|
22
|
-
if (!folderNames) {
|
|
23
|
-
console.log('Canceled static assets cleanup as folder is empty.');
|
|
24
|
-
return;
|
|
57
|
+
if (!process.env.OUTDATED_ASSETS_RETENTION_DAYS) {
|
|
58
|
+
throw new Error('Retain duration of static assets not specified!');
|
|
25
59
|
}
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.log('No outdated asset folders found.');
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
console.log(`Deleting ${outdatedFolderNames.length} outdated folders...`);
|
|
47
|
-
// Unfortunately it's not possible to delete folders recursively 🙄
|
|
48
|
-
// so we need to query all the contents in order to delete them
|
|
49
|
-
const pendingPromises = outdatedFolderNames.map(folderName => {
|
|
50
|
-
return client
|
|
51
|
-
.send(new client_s3_1.ListObjectsV2Command({
|
|
60
|
+
const retainAssetsInDays = Number.parseInt(process.env.OUTDATED_ASSETS_RETENTION_DAYS);
|
|
61
|
+
const currentRevision = await getCurrentRevision(client, bucketName);
|
|
62
|
+
const deleteOlderThan = new Date(currentRevision.getTime() - retainAssetsInDays * ONE_DAY_IN_MILLISECONDS);
|
|
63
|
+
let assetKeysToDelete = [];
|
|
64
|
+
let lastToken = undefined;
|
|
65
|
+
do {
|
|
66
|
+
const curAssetsResult = await client.send(new client_s3_1.ListObjectsV2Command({
|
|
67
|
+
Bucket: bucketName,
|
|
68
|
+
MaxKeys: 250,
|
|
69
|
+
ContinuationToken: lastToken,
|
|
70
|
+
}));
|
|
71
|
+
// Read object metadata in blocks of 10
|
|
72
|
+
let processableAssets = [...curAssetsResult.Contents];
|
|
73
|
+
while (processableAssets.length > 0) {
|
|
74
|
+
const assetsBatch = processableAssets.slice(0, 10);
|
|
75
|
+
processableAssets = processableAssets.slice(10);
|
|
76
|
+
const pendingMetadataRequests = assetsBatch.map(asset => client.send(new client_s3_1.HeadObjectCommand({
|
|
52
77
|
Bucket: bucketName,
|
|
53
|
-
|
|
54
|
-
}))
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
Key: asset.Key,
|
|
79
|
+
})));
|
|
80
|
+
const metadataResults = await Promise.all(pendingMetadataRequests);
|
|
81
|
+
// Assign metadata to assets
|
|
82
|
+
const metadataByAsset = metadataResults.map((metadataResult, index) => ({
|
|
83
|
+
key: assetsBatch[index].Key,
|
|
84
|
+
metadata: metadataResult.Metadata,
|
|
85
|
+
}));
|
|
86
|
+
const outdatedAssetKeys = filterOutdatedAssetKeys(metadataByAsset, deleteOlderThan);
|
|
87
|
+
assetKeysToDelete.push(...outdatedAssetKeys);
|
|
88
|
+
}
|
|
89
|
+
lastToken = curAssetsResult.NextContinuationToken;
|
|
90
|
+
} while (lastToken !== undefined);
|
|
91
|
+
if (assetKeysToDelete.length === 0) {
|
|
92
|
+
console.log('No outdated assets to delete found');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log('Deleting ' + assetKeysToDelete.length + ' assets...');
|
|
96
|
+
// Delete outdated assets (max. 1000 allowed per request)
|
|
97
|
+
const results = await deleteAssets(assetKeysToDelete, client, bucketName);
|
|
98
|
+
const failed = results.reduce((previousResult, currentResult) => {
|
|
99
|
+
const currentError = !!(currentResult.Errors && currentResult.Errors.length > 0);
|
|
100
|
+
if (currentError) {
|
|
101
|
+
console.error('Failed to delete outdated static assets', currentResult.Errors);
|
|
102
|
+
}
|
|
103
|
+
return previousResult || currentError;
|
|
104
|
+
}, false);
|
|
105
|
+
if (failed) {
|
|
106
|
+
throw new Error('Failed to delete outdated static assets');
|
|
75
107
|
}
|
|
76
|
-
console.log('Cleanup of old static assets finished
|
|
108
|
+
console.log('Cleanup of old static assets finished');
|
|
77
109
|
}
|
|
78
110
|
catch (error) {
|
|
79
111
|
console.error('### unexpected runtime error ###', error);
|
|
80
112
|
}
|
|
81
113
|
};
|
|
82
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLGtEQUF3RjtBQUV4RixNQUFNLHdCQUF3QixHQUFHLElBQUksR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7QUFFekQsT0FBTyxDQUFDLE9BQU8sR0FBRyxLQUFLLEVBQUUsS0FBVSxFQUFFLE9BQVksRUFBRSxFQUFFOztJQUNqRCxPQUFPLENBQUMsR0FBRyxDQUFDLHdEQUF3RCxDQUFDLENBQUM7SUFFdEUsSUFBSTtRQUNBLE1BQU0sTUFBTSxHQUFHLElBQUksb0JBQVEsQ0FBQyxFQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsRUFBQyxDQUFDLENBQUM7UUFDOUQsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxvQkFBb0IsQ0FBQztRQUVwRCxJQUFJLENBQUMsVUFBVSxFQUFFO1lBQ2IsTUFBTSxJQUFJLEtBQUssQ0FBQywwREFBMEQsQ0FBQyxDQUFDO1NBQy9FO1FBRUQsTUFBTSxlQUFlLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLHdCQUF3QixDQUFDLENBQUM7UUFFeEUsb0ZBQW9GO1FBQ3BGLE1BQU0saUJBQWlCLEdBQUcsTUFBTSxNQUFNLENBQUMsSUFBSSxDQUN2QyxJQUFJLGdDQUFvQixDQUFDO1lBQ3JCLE1BQU0sRUFBRSxVQUFVO1lBQ2xCLFNBQVMsRUFBRSxHQUFHLEVBQUUsc0NBQXNDO1NBQ3pELENBQUMsQ0FDTCxDQUFDO1FBRUYsNEVBQTRFO1FBQzVFLE1BQU0sV0FBVyxHQUFHLE1BQUEsaUJBQWlCLENBQUMsY0FBYywwQ0FBRSxHQUFHLENBQUMsTUFBTSxDQUFDLEVBQUUsV0FBQyxPQUFBLE1BQUEsTUFBTSxDQUFDLE1BQU0sMENBQUUsS0FBSyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FB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LENBQUMsR0FBRyxDQUFDLHdDQUF3QyxDQUFDLENBQUM7S0FDekQ7SUFBQyxPQUFPLEtBQUssRUFBRTtRQUNaLE9BQU8sQ0FBQyxLQUFLLENBQUMsa0NBQWtDLEVBQUUsS0FBSyxDQUFDLENBQUM7S0FDNUQ7QUFDTCxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge0RlbGV0ZU9iamVjdHNDb21tYW5kLCBMaXN0T2JqZWN0c1YyQ29tbWFuZCwgUzNDbGllbnR9IGZyb20gXCJAYXdzLXNkay9jbGllbnQtczNcIjtcblxuY29uc3QgT05FX1dFRUtfSU5fTUlMTElTRUNPTkRTID0gMTAwMCAqIDYwICogNjAgKiAyNCAqIDc7XG5cbmV4cG9ydHMuaGFuZGxlciA9IGFzeW5jIChldmVudDogYW55LCBjb250ZXh0OiBhbnkpID0+IHtcbiAgICBjb25zb2xlLmxvZygnU3RhcnRpbmcgY2xlYW51cCBvZiBzdGF0aWMgYXNzZXRzIG9sZGVyIHRoYW4gMSB3ZWVrLi4uJyk7XG5cbiAgICB0cnkge1xuICAgICAgICBjb25zdCBjbGllbnQgPSBuZXcgUzNDbGllbnQoe3JlZ2lvbjogcHJvY2Vzcy5lbnYuQVdTX1JFR0lPTn0pO1xuICAgICAgICBjb25zdCBidWNrZXROYW1lID0gcHJvY2Vzcy5lbnYuU1RBVElDX0FTU0VUU19CVUNLRVQ7XG5cbiAgICAgICAgaWYgKCFidWNrZXROYW1lKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJTdGF0aWMgYXNzZXQncyBidWNrZXQgbmFtZSBub3Qgc3BlY2lmaWVkIGluIGVudmlyb25tZW50IVwiKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnN0IGRlbGV0ZU9sZGVyVGhhbiA9IG5ldyBEYXRlKERhdGUubm93KCkgLSBPTkVfV0VFS19JTl9NSUxMSVNFQ09ORFMpO1xuXG4gICAgICAgIC8vIEFzIHdlIGRvbid0IGhhdmUgaHVuZHJlZHMgb2YgZGVwbG95bWVudHMgcGVyIHdlZWssIHRoZXJlJ3Mgbm8gbmVlZCBmb3IgcGFnaW5hdGlvblxuICAgICAgICBjb25zdCBkZXBsb3ltZW50Rm9sZGVycyA9IGF3YWl0IGNsaWVudC5zZW5kKFxuICAgICAgICAgICAgbmV3IExpc3RPYmplY3RzVjJDb21tYW5kKHtcbiAgICAgICAgICAgICAgICBCdWNrZXQ6IGJ1Y2tldE5hbWUsXG4gICAgICAgICAgICAgICAgRGVsaW1pdGVyOiAnLycsIC8vIE9ubHkgcmV0cmlldmUgdGhlIHRvcCBsZXZlbCBmb2xkZXJzXG4gICAgICAgICAgICB9KVxuICAgICAgICApO1xuXG4gICAgICAgIC8vIFRoZSByZXN1bHQgaXMgYSBsaXN0IG9mIG9iamVjdHMgY29udGFpbmluZyB0aGUga2V5cyB3aXRoIGEgdHJhaWxpbmcgc2xhc2hcbiAgICAgICAgY29uc3QgZm9sZGVyTmFtZXMgPSBkZXBsb3ltZW50Rm9sZGVycy5Db21tb25QcmVmaXhlcz8ubWFwKGZvbGRlciA9PiBmb2xkZXIuUHJlZml4Py5zbGljZSgwLCAtMSkpXG4gICAgICAgIGlmICghZm9sZGVyTmFtZXMpIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdDYW5jZWxlZCBzdGF0aWMgYXNzZXRzIGNsZWFudXAgYXMgZm9sZGVyIGlzIGVtcHR5LicpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gV2Ugc29ydCB0aGUgZm9sZGVycyBieSB0aGVpciBjcmVhdGlvbiBkYXRhIGFuZCByZW1vdmUgdGhlIGxhdGVzdCBvbmUgYXMgdGhpcyBpcyB0aGUgY3VycmVudGx5IGFjdGl2ZSBvbmUgdXNlZCBpbiBwcm9kdWN0aW9uXG4gICAgICAgIGNvbnN0IGxlZ2FjeUZvbGRlck5hbWVzID0gZm9sZGVyTmFtZXMuc29ydCgoYSxiKSA9PiB7XG4gICAgICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgICAgICBjb25zdCBjcmVhdGlvbkRhdGVBID0gbmV3IERhdGUoYSk7XG4gICAgICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgICAgICBjb25zdCBjcmVhdGlvbkRhdGVCID0gbmV3IERhdGUoYik7XG5cbiAgICAgICAgICAgIHJldHVybiBjcmVhdGlvbkRhdGVBLmdldFRpbWUoKSA8IGNyZWF0aW9uRGF0ZUIuZ2V0VGltZSgpID8gLTEgOiAxO1xuICAgICAgICB9KVxuICAgICAgICBjb25zdCBhY3RpdmVGb2xkZXJOYW1lID0gbGVnYWN5Rm9sZGVyTmFtZXMucG9wKCk7XG4gICAgICAgIGNvbnNvbGUubG9nKGBEZXRlY3RlZCBhc3NldHMgZm9sZGVyIFwiJHthY3RpdmVGb2xkZXJOYW1lfVwiIGFzIGN1cnJlbnQgcHJvZHVjdGlvbiBmb2xkZXIuLi5gKTtcblxuICAgICAgICAvLyBXZSB3YW50IHRvIGdldCBhbGwgb3V0ZGF0ZWQgZm9sZGVycyBvZiBvdXIgbGVnYWN5IChub3QgcHJvZHVjdGl2ZWx5IHVzZWQpIGZvbGRlcnMgZm9yIGRlbGV0aW9uXG4gICAgICAgIGNvbnN0IG91dGRhdGVkRm9sZGVyTmFtZXMgPSBsZWdhY3lGb2xkZXJOYW1lcy5maWx0ZXIoZm9sZGVyTmFtZSA9PiB7XG4gICAgICAgICAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICAgICAgICAgIGNvbnN0IGNyZWF0aW9uRGF0ZSA9IG5ldyBEYXRlKGZvbGRlck5hbWUpO1xuICAgICAgICAgICAgICAgIHJldHVybiBjcmVhdGlvbkRhdGUuZ2V0VGltZSgpIDwgZGVsZXRlT2xkZXJUaGFuLmdldFRpbWUoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgKTtcblxuICAgICAgICBpZiAoIW91dGRhdGVkRm9sZGVyTmFtZXMgfHwgb3V0ZGF0ZWRGb2xkZXJOYW1lcy5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKCdObyBvdXRkYXRlZCBhc3NldCBmb2xkZXJzIGZvdW5kLicpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgY29uc29sZS5sb2coYERlbGV0aW5nICR7b3V0ZGF0ZWRGb2xkZXJOYW1lcy5sZW5ndGh9IG91dGRhdGVkIGZvbGRlcnMuLi5gKTtcblxuICAgICAgICAgICAgLy8gVW5mb3J0dW5hdGVseSBpdCdzIG5vdCBwb3NzaWJsZSB0byBkZWxldGUgZm9sZGVycyByZWN1cnNpdmVseSDwn5mEXG4gICAgICAgICAgICAvLyBzbyB3ZSBuZWVkIHRvIHF1ZXJ5IGFsbCB0aGUgY29udGVudHMgaW4gb3JkZXIgdG8gZGVsZXRlIHRoZW1cbiAgICAgICAgICAgIGNvbnN0IHBlbmRpbmdQcm9taXNlcyA9IG91dGRhdGVkRm9sZGVyTmFtZXMubWFwKGZvbGRlck5hbWUgPT4ge1xuICAgICAgICAgICAgICAgIHJldHVybiBjbGllbnRcbiAgICAgICAgICAgICAgICAgICAgLnNlbmQoXG4gICAgICAgICAgICAgICAgICAgICAgICBuZXcgTGlzdE9iamVjdHNWMkNvbW1hbmQoe1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIEJ1Y2tldDogYnVja2V0TmFtZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVmaXg6IGZvbGRlck5hbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgICAgICAgICApXG4gICAgICAgICAgICAgICAgICAgIC50aGVuKG91dGRhdGVkQXNzZXRzID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBjbGllbnQuc2VuZChcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXcgRGVsZXRlT2JqZWN0c0NvbW1hbmQoe1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCdWNrZXQ6IGJ1Y2tldE5hbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERlbGV0ZToge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT2JqZWN0czogb3V0ZGF0ZWRBc3NldHMuQ29udGVudHM/Lm1hcChvdXRkYXRlZEFzc2V0ID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4ge0tleTogb3V0ZGF0ZWRBc3NldC5LZXl9O1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIGNvbnN0IHJlc3VsdHMgPSBhd2FpdCBQcm9taXNlLmFsbChwZW5kaW5nUHJvbWlzZXMpO1xuICAgICAgICAgICAgcmVzdWx0cy5mb3JFYWNoKHJlc3VsdCA9PiB7XG4gICAgICAgICAgICAgICAgaWYgKHJlc3VsdC5FcnJvcnMgJiYgcmVzdWx0LkVycm9ycy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgZXJyb3JNc2cgPSAnRmFpbGVkIHRvIGRlbGV0ZSBvdXRkYXRlZCBzdGF0aWMgYXNzZXRzLic7XG4gICAgICAgICAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyb3JNc2csIHJlc3VsdC5FcnJvcnMpO1xuICAgICAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZXJyb3JNc2cpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc29sZS5sb2coJ0NsZWFudXAgb2Ygb2xkIHN0YXRpYyBhc3NldHMgZmluaXNoZWQuJyk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcignIyMjIHVuZXhwZWN0ZWQgcnVudGltZSBlcnJvciAjIyMnLCBlcnJvcik7XG4gICAgfVxufTsiXX0=
|
|
114
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLGtEQU00QjtBQUU1QixnREFBc0M7QUFRdEMsTUFBTSxzQkFBc0IsR0FBRyxJQUFJLENBQUM7QUFDcEMsTUFBTSx1QkFBdUIsR0FBRyxJQUFJLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFFcEQ7O0dBRUc7QUFDSCxNQUFNLGtCQUFrQixHQUFHLEtBQUssRUFBRSxRQUFrQixFQUFFLFVBQWtCLEVBQWlCLEVBQUU7SUFDdkYsTUFBTSxZQUFZLEdBQUcsTUFBTSxRQUFRLENBQUMsSUFBSSxDQUNwQyxJQUFJLDRCQUFnQixDQUFDO1FBQ2pCLE1BQU0sRUFBRSxVQUFVO1FBQ2xCLEdBQUcsRUFBRSxjQUFjO0tBQ3RCLENBQUMsQ0FDTCxDQUFDO0lBRUYsT0FBTyxJQUFJLElBQUksQ0FBQyxNQUFNLElBQUEsZ0JBQUksRUFBQyxZQUFZLENBQUMsSUFBZ0IsQ0FBQyxDQUFDLENBQUM7QUFDL0QsQ0FBQyxDQUFDO0FBRUY7O0dBRUc7QUFDSCxNQUFNLHVCQUF1QixHQUFHLENBQUMsZUFBZ0MsRUFBRSxlQUFxQixFQUFZLEVBQUU7SUFDbEcsT0FBTyxlQUFlO1NBQ2pCLE1BQU0sQ0FBQyxhQUFhLENBQUMsRUFBRSxDQUNwQixhQUFhLENBQUMsUUFBUSxDQUFDLFFBQVE7UUFDM0IsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUMsT0FBTyxFQUFFLElBQUksZUFBZSxDQUFDLE9BQU8sRUFBRTtRQUNsRixDQUFDLENBQUMsS0FBSyxDQUNkO1NBQ0EsR0FBRyxDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ25ELENBQUMsQ0FBQztBQUVGOztHQUVHO0FBQ0gsTUFBTSxZQUFZLEdBQUcsS0FBSyxFQUN0QixTQUFt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QzFFLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQyxjQUF1QixFQUFFLGFBQXlDLEVBQVcsRUFBRTtZQUMxRyxNQUFNLFlBQVksR0FBWSxDQUFDLENBQUMsQ0FBQyxhQUFhLENBQUMsTUFBTSxJQUFJLGFBQWEsQ0FBQyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQzFGLElBQUksWUFBWSxFQUFFO2dCQUNkLE9BQU8sQ0FBQyxLQUFLLENBQUMseUNBQXlDLEVBQUUsYUFBYSxDQUFDLE1BQU0sQ0FBQyxDQUFDO2FBQ2xGO1lBQ0QsT0FBTyxjQUFjLElBQUksWUFBWSxDQUFDO1FBQzFDLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUVWLElBQUksTUFBTSxFQUFFO1lBQ1IsTUFBTSxJQUFJLEtBQUssQ0FBQyx5Q0FBeUMsQ0FBQyxDQUFDO1NBQzlEO1FBRUQsT0FBTyxDQUFDLEdBQUcsQ0FBQyx1Q0FBdUMsQ0FBQyxDQUFDO0tBQ3hEO0lBQUMsT0FBTyxLQUFLLEVBQUU7UUFDWixPQUFPLENBQUMsS0FBSyxDQUFDLGtDQUFrQyxFQUFFLEtBQUssQ0FBQyxDQUFDO0tBQzVEO0FBQ0wsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgICBEZWxldGVPYmplY3RzQ29tbWFuZCxcbiAgICBEZWxldGVPYmplY3RzQ29tbWFuZE91dHB1dCxcbiAgICBHZXRPYmplY3RDb21tYW5kLCBIZWFkT2JqZWN0Q29tbWFuZCxcbiAgICBMaXN0T2JqZWN0c1YyQ29tbWFuZCxcbiAgICBTM0NsaWVudFxufSBmcm9tIFwiQGF3cy1zZGsvY2xpZW50LXMzXCI7XG5pbXBvcnQgdHlwZSB7TGlzdE9iamVjdHNWMkNvbW1hbmRPdXRwdXR9IGZyb20gXCJAYXdzLXNkay9jbGllbnQtczNcIjtcbmltcG9ydCB7dGV4dH0gZnJvbSAnc3RyZWFtL2NvbnN1bWVycyc7XG5pbXBvcnQge1JlYWRhYmxlfSBmcm9tIFwic3RyZWFtXCI7XG5cbmludGVyZmFjZSBBc3NldE1ldGFkYXRhIHtcbiAgICByZWFkb25seSBrZXk6IHN0cmluZztcbiAgICByZWFkb25seSBtZXRhZGF0YToge1trZXk6IHN0cmluZ106IHN0cmluZ307XG59XG5cbmNvbnN0IE1BWF9ERUxFVEVfT0JKRUNUX0tFWVMgPSAxMDAwO1xuY29uc3QgT05FX0RBWV9JTl9NSUxMSVNFQ09ORFMgPSAxMDAwICogNjAgKiA2MCAqIDI0O1xuXG4vKipcbiAqIFJldHVybnMgdGhlIGN1cnJlbnQgZGVwbG95bWVudCByZXZpc2lvbiAoYnVpbGQgdGltZXN0YW1wKS5cbiAqL1xuY29uc3QgZ2V0Q3VycmVudFJldmlzaW9uID0gYXN5bmMgKHMzQ2xpZW50OiBTM0NsaWVudCwgYnVja2V0TmFtZTogc3RyaW5nKTogUHJvbWlzZTxEYXRlPiA9PiB7XG4gICAgY29uc3QgcmV2aXNpb25GaWxlID0gYXdhaXQgczNDbGllbnQuc2VuZChcbiAgICAgICAgbmV3IEdldE9iamVjdENvbW1hbmQoe1xuICAgICAgICAgICAgQnVja2V0OiBidWNrZXROYW1lLFxuICAgICAgICAgICAgS2V5OiAnYXBwLXJldmlzaW9uJyxcbiAgICAgICAgfSlcbiAgICApO1xuXG4gICAgcmV0dXJuIG5ldyBEYXRlKGF3YWl0IHRleHQocmV2aXNpb25GaWxlLkJvZHkgYXMgUmVhZGFibGUpKTtcbn07XG5cbi8qKlxuICogRmlsdGVycyB0aGUgZ2l2ZW4gYXNzZXRzIGJ5IGluc3BlY3RpbmcgdGhlaXIgcmV2aXNpb24gYW5kIHJldHVybnMgdGhvc2UsIHRoYXQgYXJlIG9sZGVyIHRoYW4gdGhlIHNwZWNpZmllZCBjdXRvZmYgZGF0ZS5cbiAqL1xuY29uc3QgZmlsdGVyT3V0ZGF0ZWRBc3NldEtleXMgPSAobWV0YWRhdGFSZXN1bHRzOiBBc3NldE1ldGFkYXRhW10sIHJldHVybk9sZGVyVGhhbjogRGF0ZSk6IHN0cmluZ1tdID0+IHtcbiAgICByZXR1cm4gbWV0YWRhdGFSZXN1bHRzXG4gICAgICAgIC5maWx0ZXIoYXNzZXRNZXRhZGF0YSA9PlxuICAgICAgICAgICAgYXNzZXRNZXRhZGF0YS5tZXRhZGF0YS5yZXZpc2lvblxuICAgICAgICAgICAgICAgID8gbmV3IERhdGUoYXNzZXRNZXRhZGF0YS5tZXRhZGF0YS5yZXZpc2lvbikuZ2V0VGltZSgpIDw9IHJldHVybk9sZGVyVGhhbi5nZXRUaW1lKClcbiAgICAgICAgICAgICAgICA6IGZhbHNlXG4gICAgICAgIClcbiAgICAgICAgLm1hcChmaWx0ZXJlZEFzc2V0cyA9PiBmaWx0ZXJlZEFzc2V0cy5rZXkpO1xufTtcblxuLyoqXG4gKiBEZWxldGVzIHRoZSBnaXZlbiBhc3NldHMuXG4gKi9cbmNvbnN0IGRlbGV0ZUFzc2V0cyA9IGFzeW5jIChcbiAgICBhc3NldEtleXM6IHN0cmluZ1tdLFxuICAgIHMzQ2xpZW50OiBTM0NsaWVudCxcbiAgICBidWNrZXROYW1lOiBzdHJpbmdcbik6IFByb21pc2U8RGVsZXRlT2JqZWN0c0NvbW1hbmRPdXRwdXRbXT4gPT4ge1xuICAgIGxldCByZW1haW5pbmdBc3NldEtleXNUb0RlbGV0ZSA9IFsuLi5hc3NldEtleXNdO1xuICAgIGNvbnN0IHBlbmRpbmdEZWxldGVzID0gW107XG5cbiAgICB3aGlsZSAocmVtYWluaW5nQXNzZXRLZXlzVG9EZWxldGUubGVuZ3RoID4gMCkge1xuICAgICAgICBjb25zdCBjdXJEZWxldGVCYXRjaCA9IHJlbWFpbmluZ0Fzc2V0S2V5c1RvRGVsZXRlLnNsaWNlKDAsIE1BWF9ERUxFVEVfT0JKRUNUX0tFWVMpO1xuICAgICAgICByZW1haW5pbmdBc3NldEtleXNUb0RlbGV0ZSA9IHJlbWFpbmluZ0Fzc2V0S2V5c1RvRGVsZXRlLnNsaWNlKE1BWF9ERUxFVEVfT0JKRUNUX0tFWVMpO1xuXG4gICAgICAgIGNvbnNvbGUubG9nKCdEZWxldGluZyBhc3NldHM6JywgY3VyRGVsZXRlQmF0Y2gpO1xuXG4gICAgICAgIGNvbnN0IHBlbmRpbmdEZWxldGUgPSBzM0NsaWVudC5zZW5kKFxuICAgICAgICAgICAgbmV3IERlbGV0ZU9iamVjdHNDb21tYW5kKHtcbiAgICAgICAgICAgICAgICBCdWNrZXQ6IGJ1Y2tldE5hbWUsXG4gICAgICAgICAgICAgICAgRGVsZXRlOiB7XG4gICAgICAgICAgICAgICAgICAgIE9iamVjdHM6IGN1ckRlbGV0ZUJhdGNoLm1hcChvdXRkYXRlZEtleSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4ge0tleTogb3V0ZGF0ZWRLZXl9O1xuICAgICAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSlcbiAgICAgICAgKTtcbiAgICAgICAgcGVuZGluZ0RlbGV0ZXMucHVzaChwZW5kaW5nRGVsZXRlKTtcbiAgICB9XG5cbiAgICByZXR1cm4gYXdhaXQgUHJvbWlzZS5hbGwocGVuZGluZ0RlbGV0ZXMpO1xufTtcblxuZXhwb3J0cy5oYW5kbGVyID0gYXN5bmMgKGV2ZW50OiBhbnksIGNvbnRleHQ6IGFueSkgPT4ge1xuICAgIGNvbnNvbGUubG9nKCdTdGFydGluZyBjbGVhbnVwIG9mIHN0YXRpYyBhc3NldHMgb2xkZXIgdGhhbiAxIHdlZWsuLi4nKTtcblxuICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGNsaWVudCA9IG5ldyBTM0NsaWVudCh7cmVnaW9uOiBwcm9jZXNzLmVudi5BV1NfUkVHSU9OfSk7XG4gICAgICAgIGNvbnN0IGJ1Y2tldE5hbWUgPSBwcm9jZXNzLmVudi5TVEFUSUNfQVNTRVRTX0JVQ0tFVDtcbiAgICAgICAgaWYgKCFidWNrZXROYW1lKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJTdGF0aWMgYXNzZXQncyBidWNrZXQgbmFtZSBub3Qgc3BlY2lmaWVkIGluIGVudmlyb25tZW50IVwiKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghcHJvY2Vzcy5lbnYuT1VUREFURURfQVNTRVRTX1JFVEVOVElPTl9EQVlTKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1JldGFpbiBkdXJhdGlvbiBvZiBzdGF0aWMgYXNzZXRzIG5vdCBzcGVjaWZpZWQhJyk7XG4gICAgICAgIH1cbiAgICAgICAgY29uc3QgcmV0YWluQXNzZXRzSW5EYXlzID0gTnVtYmVyLnBhcnNlSW50KHByb2Nlc3MuZW52Lk9VVERBVEVEX0FTU0VUU19SRVRFTlRJT05fREFZUyk7XG4gICAgICAgIGNvbnN0IGN1cnJlbnRSZXZpc2lvbiA9IGF3YWl0IGdldEN1cnJlbnRSZXZpc2lvbihjbGllbnQsIGJ1Y2tldE5hbWUpO1xuICAgICAgICBjb25zdCBkZWxldGVPbGRlclRoYW4gPSBuZXcgRGF0ZShjdXJyZW50UmV2aXNpb24uZ2V0VGltZSgpIC0gcmV0YWluQXNzZXRzSW5EYXlzICogT05FX0RBWV9JTl9NSUxMSVNFQ09ORFMpO1xuXG4gICAgICAgIGxldCBhc3NldEtleXNUb0RlbGV0ZTogc3RyaW5nW10gPSBbXTtcbiAgICAgICAgbGV0IGxhc3RUb2tlbiA9IHVuZGVmaW5lZDtcblxuICAgICAgICBkbyB7XG4gICAgICAgICAgICBjb25zdCBjdXJBc3NldHNSZXN1bHQ6IExpc3RPYmplY3RzVjJDb21tYW5kT3V0cHV0ID0gYXdhaXQgY2xpZW50LnNlbmQoXG4gICAgICAgICAgICAgICAgbmV3IExpc3RPYmplY3RzVjJDb21tYW5kKHtcbiAgICAgICAgICAgICAgICAgICAgQnVja2V0OiBidWNrZXROYW1lLFxuICAgICAgICAgICAgICAgICAgICBNYXhLZXlzOiAyNTAsXG4gICAgICAgICAgICAgICAgICAgIENvbnRpbnVhdGlvblRva2VuOiBsYXN0VG9rZW4sXG4gICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICk7XG5cbiAgICAgICAgICAgIC8vIFJlYWQgb2JqZWN0IG1ldGFkYXRhIGluIGJsb2NrcyBvZiAxMFxuICAgICAgICAgICAgbGV0IHByb2Nlc3NhYmxlQXNzZXRzID0gWy4uLmN1ckFzc2V0c1Jlc3VsdC5Db250ZW50cyFdO1xuXG4gICAgICAgICAgICB3aGlsZSAocHJvY2Vzc2FibGVBc3NldHMubGVuZ3RoID4gMCkge1xuICAgICAgICAgICAgICAgIGNvbnN0IGFzc2V0c0JhdGNoID0gcHJvY2Vzc2FibGVBc3NldHMuc2xpY2UoMCwgMTApO1xuICAgICAgICAgICAgICAgIHByb2Nlc3NhYmxlQXNzZXRzID0gcHJvY2Vzc2FibGVBc3NldHMuc2xpY2UoMTApO1xuXG4gICAgICAgICAgICAgICAgY29uc3QgcGVuZGluZ01ldGFkYXRhUmVxdWVzdHMgPSBhc3NldHNCYXRjaC5tYXAoYXNzZXQgPT5cbiAgICAgICAgICAgICAgICAgICAgY2xpZW50LnNlbmQoXG4gICAgICAgICAgICAgICAgICAgICAgICBuZXcgSGVhZE9iamVjdENvbW1hbmQoe1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIEJ1Y2tldDogYnVja2V0TmFtZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBLZXk6IGFzc2V0LktleSxcbiAgICAgICAgICAgICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAgICAgICAgIClcbiAgICAgICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAgICAgY29uc3QgbWV0YWRhdGFSZXN1bHRzID0gYXdhaXQgUHJvbWlzZS5hbGwocGVuZGluZ01ldGFkYXRhUmVxdWVzdHMpO1xuXG4gICAgICAgICAgICAgICAgLy8gQXNzaWduIG1ldGFkYXRhIHRvIGFzc2V0c1xuICAgICAgICAgICAgICAgIGNvbnN0IG1ldGFkYXRhQnlBc3NldDogQXNzZXRNZXRhZGF0YVtdID0gKG1ldGFkYXRhUmVzdWx0cy5tYXAoKG1ldGFkYXRhUmVzdWx0LCBpbmRleCkgPT4gKHtcbiAgICAgICAgICAgICAgICAgICAga2V5OiBhc3NldHNCYXRjaFtpbmRleF0uS2V5LFxuICAgICAgICAgICAgICAgICAgICBtZXRhZGF0YTogbWV0YWRhdGFSZXN1bHQuTWV0YWRhdGEsXG4gICAgICAgICAgICAgICAgfSkpIGFzIEFzc2V0TWV0YWRhdGFbXSk7XG5cbiAgICAgICAgICAgICAgICBjb25zdCBvdXRkYXRlZEFzc2V0S2V5cyA9IGZpbHRlck91dGRhdGVkQXNzZXRLZXlzKG1ldGFkYXRhQnlBc3NldCwgZGVsZXRlT2xkZXJUaGFuKTtcbiAgICAgICAgICAgICAgICBhc3NldEtleXNUb0RlbGV0ZS5wdXNoKC4uLm91dGRhdGVkQXNzZXRLZXlzKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGxhc3RUb2tlbiA9IGN1ckFzc2V0c1Jlc3VsdC5OZXh0Q29udGludWF0aW9uVG9rZW47XG4gICAgICAgIH0gd2hpbGUgKGxhc3RUb2tlbiAhPT0gdW5kZWZpbmVkKTtcblxuICAgICAgICBpZiAoYXNzZXRLZXlzVG9EZWxldGUubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygnTm8gb3V0ZGF0ZWQgYXNzZXRzIHRvIGRlbGV0ZSBmb3VuZCcpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgY29uc29sZS5sb2coJ0RlbGV0aW5nICcgKyBhc3NldEtleXNUb0RlbGV0ZS5sZW5ndGggKyAnIGFzc2V0cy4uLicpO1xuXG4gICAgICAgIC8vIERlbGV0ZSBvdXRkYXRlZCBhc3NldHMgKG1heC4gMTAwMCBhbGxvd2VkIHBlciByZXF1ZXN0KVxuICAgICAgICBjb25zdCByZXN1bHRzID0gYXdhaXQgZGVsZXRlQXNzZXRzKGFzc2V0S2V5c1RvRGVsZXRlLCBjbGllbnQsIGJ1Y2tldE5hbWUpO1xuICAgICAgICBjb25zdCBmYWlsZWQgPSByZXN1bHRzLnJlZHVjZSgocHJldmlvdXNSZXN1bHQ6IGJvb2xlYW4sIGN1cnJlbnRSZXN1bHQ6IERlbGV0ZU9iamVjdHNDb21tYW5kT3V0cHV0KTogYm9vbGVhbiA9PiB7XG4gICAgICAgICAgICBjb25zdCBjdXJyZW50RXJyb3I6IGJvb2xlYW4gPSAhIShjdXJyZW50UmVzdWx0LkVycm9ycyAmJiBjdXJyZW50UmVzdWx0LkVycm9ycy5sZW5ndGggPiAwKTtcbiAgICAgICAgICAgIGlmIChjdXJyZW50RXJyb3IpIHtcbiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKCdGYWlsZWQgdG8gZGVsZXRlIG91dGRhdGVkIHN0YXRpYyBhc3NldHMnLCBjdXJyZW50UmVzdWx0LkVycm9ycyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gcHJldmlvdXNSZXN1bHQgfHwgY3VycmVudEVycm9yO1xuICAgICAgICB9LCBmYWxzZSk7XG5cbiAgICAgICAgaWYgKGZhaWxlZCkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdGYWlsZWQgdG8gZGVsZXRlIG91dGRhdGVkIHN0YXRpYyBhc3NldHMnKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnNvbGUubG9nKCdDbGVhbnVwIG9mIG9sZCBzdGF0aWMgYXNzZXRzIGZpbmlzaGVkJyk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgY29uc29sZS5lcnJvcignIyMjIHVuZXhwZWN0ZWQgcnVudGltZSBlcnJvciAjIyMnLCBlcnJvcik7XG4gICAgfVxufTsiXX0=
|