cdk-nuxt 0.1.1 → 0.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/lib/cli/deploy.js +20 -0
- package/lib/functions/assets_cleanup/index.d.ts +1 -0
- package/lib/functions/assets_cleanup/index.js +82 -0
- package/lib/functions/assets_cleanup/index.ts +96 -0
- package/lib/functions/assets_cleanup/package.json +19 -0
- package/lib/functions/assets_cleanup/tsconfig.json +35 -0
- package/lib/functions/assets_cleanup/yarn.lock +959 -0
- package/lib/server/lambda-handler.js +4 -0
- package/lib/stack/app-stack-props.d.ts +6 -0
- package/lib/stack/app-stack-props.js +3 -0
- package/lib/stack/app-stack-props.ts +7 -0
- package/lib/stack/nuxt-app-assets-cleanup-stack.d.ts +22 -0
- package/lib/stack/nuxt-app-assets-cleanup-stack.js +68 -0
- package/lib/stack/nuxt-app-assets-cleanup-stack.ts +88 -0
- package/lib/stack/nuxt-app-stack.d.ts +46 -0
- package/lib/stack/nuxt-app-stack.js +223 -0
- package/lib/{nuxt-app-stack.ts → stack/nuxt-app-stack.ts} +9 -13
- package/lib/stack/nuxt-app-static-assets.d.ts +10 -0
- package/lib/stack/nuxt-app-static-assets.js +98 -0
- package/lib/{nuxt-app-static-assets.ts → stack/nuxt-app-static-assets.ts} +0 -0
- package/package.json +11 -5
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const shell = require("shelljs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
require('dotenv').config()
|
|
6
|
+
|
|
7
|
+
const deploymentFolder = '.nuxt/cdk-deployment';
|
|
8
|
+
|
|
9
|
+
shell.rm('-rf', deploymentFolder);
|
|
10
|
+
shell.mkdir('-p', `${deploymentFolder}/.nuxt/dist`);
|
|
11
|
+
|
|
12
|
+
shell.cp('-r', '.nuxt/dist/server', `${deploymentFolder}/.nuxt/dist`);
|
|
13
|
+
|
|
14
|
+
shell.cp(path.join(__dirname, '../server/lambda-handler.js'), deploymentFolder);
|
|
15
|
+
|
|
16
|
+
shell.cp('.env', deploymentFolder);
|
|
17
|
+
shell.cp('nuxt.config.js', deploymentFolder);
|
|
18
|
+
|
|
19
|
+
const entryPoint = path.join(__dirname, '../index.ts')
|
|
20
|
+
shell.exec(`yarn cdk deploy --require-approval never --all --app="yarn ts-node ${entryPoint}"`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
4
|
+
const ONE_WEEK_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 7;
|
|
5
|
+
exports.handler = async (event, context) => {
|
|
6
|
+
var _a;
|
|
7
|
+
console.log('Starting cleanup of static assets older than 1 week...');
|
|
8
|
+
try {
|
|
9
|
+
const client = new client_s3_1.S3Client({ region: process.env.AWS_REGION });
|
|
10
|
+
const bucketName = process.env.STATIC_ASSETS_BUCKET;
|
|
11
|
+
if (!bucketName) {
|
|
12
|
+
throw new Error("Static asset's bucket name not specified in environment!");
|
|
13
|
+
}
|
|
14
|
+
const deleteOlderThan = new Date(Date.now() - ONE_WEEK_IN_MILLISECONDS);
|
|
15
|
+
// As we don't have hundreds of deployments per week, there's no need for pagination
|
|
16
|
+
const deploymentFolders = await client.send(new client_s3_1.ListObjectsV2Command({
|
|
17
|
+
Bucket: bucketName,
|
|
18
|
+
Delimiter: '/',
|
|
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;
|
|
25
|
+
}
|
|
26
|
+
// We sort the folders by their creation data and remove the latest one as this is the currently active one used in production
|
|
27
|
+
const legacyFolderNames = folderNames.sort((a, b) => {
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
const creationDateA = new Date(a);
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
const creationDateB = new Date(b);
|
|
32
|
+
return creationDateA.getTime() < creationDateB.getTime() ? -1 : 1;
|
|
33
|
+
});
|
|
34
|
+
const activeFolderName = legacyFolderNames.pop();
|
|
35
|
+
console.log(`Detected assets folder "${activeFolderName}" as current production folder...`);
|
|
36
|
+
// We want to get all outdated folders of our legacy (not productively used) folders for deletion
|
|
37
|
+
const outdatedFolderNames = legacyFolderNames.filter(folderName => {
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
const creationDate = new Date(folderName);
|
|
40
|
+
return creationDate.getTime() < deleteOlderThan.getTime();
|
|
41
|
+
});
|
|
42
|
+
if (!outdatedFolderNames || outdatedFolderNames.length === 0) {
|
|
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({
|
|
52
|
+
Bucket: bucketName,
|
|
53
|
+
Prefix: folderName,
|
|
54
|
+
}))
|
|
55
|
+
.then(outdatedAssets => {
|
|
56
|
+
var _a;
|
|
57
|
+
return client.send(new client_s3_1.DeleteObjectsCommand({
|
|
58
|
+
Bucket: bucketName,
|
|
59
|
+
Delete: {
|
|
60
|
+
Objects: (_a = outdatedAssets.Contents) === null || _a === void 0 ? void 0 : _a.map(outdatedAsset => {
|
|
61
|
+
return { Key: outdatedAsset.Key };
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
const results = await Promise.all(pendingPromises);
|
|
68
|
+
results.forEach(result => {
|
|
69
|
+
if (result.Errors && result.Errors.length) {
|
|
70
|
+
const errorMsg = 'Failed to delete outdated static assets.';
|
|
71
|
+
console.error(errorMsg, result.Errors);
|
|
72
|
+
throw new Error(errorMsg);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
console.log('Cleanup of old static assets finished.');
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error('### unexpected runtime error ###', error);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLGtEQUF3RjtBQUV4RixNQUFNLHdCQUF3QixHQUFHLElBQUksR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7QUFFekQsT0FBTyxDQUFDLE9BQU8sR0FBRyxLQUFLLEVBQUUsS0FBVSxFQUFFLE9BQVksRUFBRSxFQUFFOztJQUNqRCxPQUFPLENBQUMsR0FBRyxDQUFDLHdEQUF3RCxDQUFDLENBQUM7SUFFdEUsSUFBSTtRQUNBLE1BQU0sTUFBTSxHQUFHLElBQUksb0JBQVEsQ0FBQyxFQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsRUFBQyxDQUFDLENBQUM7UUFDOUQsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxvQkFBb0IsQ0FBQztRQUVwRCxJQUFJLENBQUMsVUFBVSxFQUFFO1lBQ2IsTUFBTSxJQUFJLEtBQUssQ0FBQywwREFBMEQsQ0FBQyxDQUFDO1NBQy9FO1FBRUQsTUFBTSxlQUFlLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLHdCQUF3QixDQUFDLENBQUM7UUFFeEUsb0ZBQW9GO1FBQ3BGLE1BQU0saUJBQWlCLEdBQUcsTUFBTSxNQUFNLENBQUMsSUFBSSxDQUN2QyxJQUFJLGdDQUFvQixDQUFDO1lBQ3JCLE1BQU0sRUFBRSxVQUFVO1lBQ2xCLFNBQVMsRUFBRSxHQUFHO1NBQ2pCLENBQUMsQ0FDTCxDQUFDO1FBRUYsNEVBQTRFO1FBQzVFLE1BQU0sV0FBVyxTQUFHLGlCQUFpQixDQUFDLGNBQWMsMENBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFLHdCQUFDLE1BQU0sQ0FBQyxNQUFNLDBDQUFFLEtBQUssQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLElBQUMsQ0FBQyxDQUFBO1FBQ2hHLElBQUksQ0FB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QUMsT0FBTyxLQUFLLEVBQUU7UUFDWixPQUFPLENBQUMsS0FBSyxDQUFDLGtDQUFrQyxFQUFFLEtBQUssQ0FBQyxDQUFDO0tBQzVEO0FBQ0wsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtEZWxldGVPYmplY3RzQ29tbWFuZCwgTGlzdE9iamVjdHNWMkNvbW1hbmQsIFMzQ2xpZW50fSBmcm9tIFwiQGF3cy1zZGsvY2xpZW50LXMzXCI7XG5cbmNvbnN0IE9ORV9XRUVLX0lOX01JTExJU0VDT05EUyA9IDEwMDAgKiA2MCAqIDYwICogMjQgKiA3O1xuXG5leHBvcnRzLmhhbmRsZXIgPSBhc3luYyAoZXZlbnQ6IGFueSwgY29udGV4dDogYW55KSA9PiB7XG4gICAgY29uc29sZS5sb2coJ1N0YXJ0aW5nIGNsZWFudXAgb2Ygc3RhdGljIGFzc2V0cyBvbGRlciB0aGFuIDEgd2Vlay4uLicpO1xuXG4gICAgdHJ5IHtcbiAgICAgICAgY29uc3QgY2xpZW50ID0gbmV3IFMzQ2xpZW50KHtyZWdpb246IHByb2Nlc3MuZW52LkFXU19SRUdJT059KTtcbiAgICAgICAgY29uc3QgYnVja2V0TmFtZSA9IHByb2Nlc3MuZW52LlNUQVRJQ19BU1NFVFNfQlVDS0VUO1xuXG4gICAgICAgIGlmICghYnVja2V0TmFtZSkge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiU3RhdGljIGFzc2V0J3MgYnVja2V0IG5hbWUgbm90IHNwZWNpZmllZCBpbiBlbnZpcm9ubWVudCFcIik7XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBkZWxldGVPbGRlclRoYW4gPSBuZXcgRGF0ZShEYXRlLm5vdygpIC0gT05FX1dFRUtfSU5fTUlMTElTRUNPTkRTKTtcblxuICAgICAgICAvLyBBcyB3ZSBkb24ndCBoYXZlIGh1bmRyZWRzIG9mIGRlcGxveW1lbnRzIHBlciB3ZWVrLCB0aGVyZSdzIG5vIG5lZWQgZm9yIHBhZ2luYXRpb25cbiAgICAgICAgY29uc3QgZGVwbG95bWVudEZvbGRlcnMgPSBhd2FpdCBjbGllbnQuc2VuZChcbiAgICAgICAgICAgIG5ldyBMaXN0T2JqZWN0c1YyQ29tbWFuZCh7XG4gICAgICAgICAgICAgICAgQnVja2V0OiBidWNrZXROYW1lLFxuICAgICAgICAgICAgICAgIERlbGltaXRlcjogJy8nLCAvLyBPbmx5IHJldHJpZXZlIHRoZSB0b3AgbGV2ZWwgZm9sZGVyc1xuICAgICAgICAgICAgfSlcbiAgICAgICAgKTtcblxuICAgICAgICAvLyBUaGUgcmVzdWx0IGlzIGEgbGlzdCBvZiBvYmplY3RzIGNvbnRhaW5pbmcgdGhlIGtleXMgd2l0aCBhIHRyYWlsaW5nIHNsYXNoXG4gICAgICAgIGNvbnN0IGZvbGRlck5hbWVzID0gZGVwbG95bWVudEZvbGRlcnMuQ29tbW9uUHJlZml4ZXM/Lm1hcChmb2xkZXIgPT4gZm9sZGVyLlByZWZpeD8uc2xpY2UoMCwgLTEpKVxuICAgICAgICBpZiAoIWZvbGRlck5hbWVzKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygnQ2FuY2VsZWQgc3RhdGljIGFzc2V0cyBjbGVhbnVwIGFzIGZvbGRlciBpcyBlbXB0eS4nKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFdlIHNvcnQgdGhlIGZvbGRlcnMgYnkgdGhlaXIgY3JlYXRpb24gZGF0YSBhbmQgcmVtb3ZlIHRoZSBsYXRlc3Qgb25lIGFzIHRoaXMgaXMgdGhlIGN1cnJlbnRseSBhY3RpdmUgb25lIHVzZWQgaW4gcHJvZHVjdGlvblxuICAgICAgICBjb25zdCBsZWdhY3lGb2xkZXJOYW1lcyA9IGZvbGRlck5hbWVzLnNvcnQoKGEsYikgPT4ge1xuICAgICAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICAgICAgY29uc3QgY3JlYXRpb25EYXRlQSA9IG5ldyBEYXRlKGEpO1xuICAgICAgICAgICAgLy8gQHRzLWlnbm9yZVxuICAgICAgICAgICAgY29uc3QgY3JlYXRpb25EYXRlQiA9IG5ldyBEYXRlKGIpO1xuXG4gICAgICAgICAgICByZXR1cm4gY3JlYXRpb25EYXRlQS5nZXRUaW1lKCkgPCBjcmVhdGlvbkRhdGVCLmdldFRpbWUoKSA/IC0xIDogMTtcbiAgICAgICAgfSlcbiAgICAgICAgY29uc3QgYWN0aXZlRm9sZGVyTmFtZSA9IGxlZ2FjeUZvbGRlck5hbWVzLnBvcCgpO1xuICAgICAgICBjb25zb2xlLmxvZyhgRGV0ZWN0ZWQgYXNzZXRzIGZvbGRlciBcIiR7YWN0aXZlRm9sZGVyTmFtZX1cIiBhcyBjdXJyZW50IHByb2R1Y3Rpb24gZm9sZGVyLi4uYCk7XG5cbiAgICAgICAgLy8gV2Ugd2FudCB0byBnZXQgYWxsIG91dGRhdGVkIGZvbGRlcnMgb2Ygb3VyIGxlZ2FjeSAobm90IHByb2R1Y3RpdmVseSB1c2VkKSBmb2xkZXJzIGZvciBkZWxldGlvblxuICAgICAgICBjb25zdCBvdXRkYXRlZEZvbGRlck5hbWVzID0gbGVnYWN5Rm9sZGVyTmFtZXMuZmlsdGVyKGZvbGRlck5hbWUgPT4ge1xuICAgICAgICAgICAgICAgIC8vIEB0cy1pZ25vcmVcbiAgICAgICAgICAgICAgICBjb25zdCBjcmVhdGlvbkRhdGUgPSBuZXcgRGF0ZShmb2xkZXJOYW1lKTtcbiAgICAgICAgICAgICAgICByZXR1cm4gY3JlYXRpb25EYXRlLmdldFRpbWUoKSA8IGRlbGV0ZU9sZGVyVGhhbi5nZXRUaW1lKCk7XG4gICAgICAgICAgICB9XG4gICAgICAgICk7XG5cbiAgICAgICAgaWYgKCFvdXRkYXRlZEZvbGRlck5hbWVzIHx8IG91dGRhdGVkRm9sZGVyTmFtZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICBjb25zb2xlLmxvZygnTm8gb3V0ZGF0ZWQgYXNzZXQgZm9sZGVycyBmb3VuZC4nKTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGNvbnNvbGUubG9nKGBEZWxldGluZyAke291dGRhdGVkRm9sZGVyTmFtZXMubGVuZ3RofSBvdXRkYXRlZCBmb2xkZXJzLi4uYCk7XG5cbiAgICAgICAgICAgIC8vIFVuZm9ydHVuYXRlbHkgaXQncyBub3QgcG9zc2libGUgdG8gZGVsZXRlIGZvbGRlcnMgcmVjdXJzaXZlbHkg8J+ZhFxuICAgICAgICAgICAgLy8gc28gd2UgbmVlZCB0byBxdWVyeSBhbGwgdGhlIGNvbnRlbnRzIGluIG9yZGVyIHRvIGRlbGV0ZSB0aGVtXG4gICAgICAgICAgICBjb25zdCBwZW5kaW5nUHJvbWlzZXMgPSBvdXRkYXRlZEZvbGRlck5hbWVzLm1hcChmb2xkZXJOYW1lID0+IHtcbiAgICAgICAgICAgICAgICByZXR1cm4gY2xpZW50XG4gICAgICAgICAgICAgICAgICAgIC5zZW5kKFxuICAgICAgICAgICAgICAgICAgICAgICAgbmV3IExpc3RPYmplY3RzVjJDb21tYW5kKHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBCdWNrZXQ6IGJ1Y2tldE5hbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlZml4OiBmb2xkZXJOYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgICAgICAgICAudGhlbihvdXRkYXRlZEFzc2V0cyA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gY2xpZW50LnNlbmQoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IERlbGV0ZU9iamVjdHNDb21tYW5kKHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQnVja2V0OiBidWNrZXROYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEZWxldGU6IHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9iamVjdHM6IG91dGRhdGVkQXNzZXRzLkNvbnRlbnRzPy5tYXAob3V0ZGF0ZWRBc3NldCA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHtLZXk6IG91dGRhdGVkQXNzZXQuS2V5fTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAgICAgICAgICAgICApO1xuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICBjb25zdCByZXN1bHRzID0gYXdhaXQgUHJvbWlzZS5hbGwocGVuZGluZ1Byb21pc2VzKTtcbiAgICAgICAgICAgIHJlc3VsdHMuZm9yRWFjaChyZXN1bHQgPT4ge1xuICAgICAgICAgICAgICAgIGlmIChyZXN1bHQuRXJyb3JzICYmIHJlc3VsdC5FcnJvcnMubGVuZ3RoKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGVycm9yTXNnID0gJ0ZhaWxlZCB0byBkZWxldGUgb3V0ZGF0ZWQgc3RhdGljIGFzc2V0cy4nO1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKGVycm9yTXNnLCByZXN1bHQuRXJyb3JzKTtcbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGVycm9yTXNnKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGNvbnNvbGUubG9nKCdDbGVhbnVwIG9mIG9sZCBzdGF0aWMgYXNzZXRzIGZpbmlzaGVkLicpO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGNvbnNvbGUuZXJyb3IoJyMjIyB1bmV4cGVjdGVkIHJ1bnRpbWUgZXJyb3IgIyMjJywgZXJyb3IpO1xuICAgIH1cbn07Il19
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {DeleteObjectsCommand, ListObjectsV2Command, S3Client} from "@aws-sdk/client-s3";
|
|
2
|
+
|
|
3
|
+
const ONE_WEEK_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 7;
|
|
4
|
+
|
|
5
|
+
exports.handler = async (event: any, context: any) => {
|
|
6
|
+
console.log('Starting cleanup of static assets older than 1 week...');
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const client = new S3Client({region: process.env.AWS_REGION});
|
|
10
|
+
const bucketName = process.env.STATIC_ASSETS_BUCKET;
|
|
11
|
+
|
|
12
|
+
if (!bucketName) {
|
|
13
|
+
throw new Error("Static asset's bucket name not specified in environment!");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const deleteOlderThan = new Date(Date.now() - ONE_WEEK_IN_MILLISECONDS);
|
|
17
|
+
|
|
18
|
+
// As we don't have hundreds of deployments per week, there's no need for pagination
|
|
19
|
+
const deploymentFolders = await client.send(
|
|
20
|
+
new ListObjectsV2Command({
|
|
21
|
+
Bucket: bucketName,
|
|
22
|
+
Delimiter: '/', // Only retrieve the top level folders
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// The result is a list of objects containing the keys with a trailing slash
|
|
27
|
+
const folderNames = deploymentFolders.CommonPrefixes?.map(folder => folder.Prefix?.slice(0, -1))
|
|
28
|
+
if (!folderNames) {
|
|
29
|
+
console.log('Canceled static assets cleanup as folder is empty.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// We sort the folders by their creation data and remove the latest one as this is the currently active one used in production
|
|
34
|
+
const legacyFolderNames = folderNames.sort((a,b) => {
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
const creationDateA = new Date(a);
|
|
37
|
+
// @ts-ignore
|
|
38
|
+
const creationDateB = new Date(b);
|
|
39
|
+
|
|
40
|
+
return creationDateA.getTime() < creationDateB.getTime() ? -1 : 1;
|
|
41
|
+
})
|
|
42
|
+
const activeFolderName = legacyFolderNames.pop();
|
|
43
|
+
console.log(`Detected assets folder "${activeFolderName}" as current production folder...`);
|
|
44
|
+
|
|
45
|
+
// We want to get all outdated folders of our legacy (not productively used) folders for deletion
|
|
46
|
+
const outdatedFolderNames = legacyFolderNames.filter(folderName => {
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
const creationDate = new Date(folderName);
|
|
49
|
+
return creationDate.getTime() < deleteOlderThan.getTime();
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (!outdatedFolderNames || outdatedFolderNames.length === 0) {
|
|
54
|
+
console.log('No outdated asset folders found.');
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`Deleting ${outdatedFolderNames.length} outdated folders...`);
|
|
57
|
+
|
|
58
|
+
// Unfortunately it's not possible to delete folders recursively 🙄
|
|
59
|
+
// so we need to query all the contents in order to delete them
|
|
60
|
+
const pendingPromises = outdatedFolderNames.map(folderName => {
|
|
61
|
+
return client
|
|
62
|
+
.send(
|
|
63
|
+
new ListObjectsV2Command({
|
|
64
|
+
Bucket: bucketName,
|
|
65
|
+
Prefix: folderName,
|
|
66
|
+
})
|
|
67
|
+
)
|
|
68
|
+
.then(outdatedAssets => {
|
|
69
|
+
return client.send(
|
|
70
|
+
new DeleteObjectsCommand({
|
|
71
|
+
Bucket: bucketName,
|
|
72
|
+
Delete: {
|
|
73
|
+
Objects: outdatedAssets.Contents?.map(outdatedAsset => {
|
|
74
|
+
return {Key: outdatedAsset.Key};
|
|
75
|
+
}),
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const results = await Promise.all(pendingPromises);
|
|
83
|
+
results.forEach(result => {
|
|
84
|
+
if (result.Errors && result.Errors.length) {
|
|
85
|
+
const errorMsg = 'Failed to delete outdated static assets.';
|
|
86
|
+
console.error(errorMsg, result.Errors);
|
|
87
|
+
throw new Error(errorMsg);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log('Cleanup of old static assets finished.');
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error('### unexpected runtime error ###', error);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cdk-nuxt-assets-cleanup",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"private": true,
|
|
7
|
+
"scripts": {
|
|
8
|
+
"install-layer": "yarn install --frozen-lockfile --production --modules-folder build/layer/nodejs/node_modules",
|
|
9
|
+
"build": "tsc --project ./tsconfig.json"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/node": "^16.11.0",
|
|
13
|
+
"ts-node": "^10.3.0",
|
|
14
|
+
"typescript": "^4.4.0"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@aws-sdk/client-s3": "^3.37.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "./build/app",
|
|
4
|
+
"target": "ES2021",
|
|
5
|
+
"module": "CommonJS",
|
|
6
|
+
"lib": [
|
|
7
|
+
"es2021"
|
|
8
|
+
],
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"alwaysStrict": true,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"inlineSources": true,
|
|
13
|
+
"inlineSourceMap": false,
|
|
14
|
+
"sourceRoot": "/",
|
|
15
|
+
"noEmitHelpers": true,
|
|
16
|
+
"importHelpers": true,
|
|
17
|
+
"noImplicitAny": false,
|
|
18
|
+
"noUnusedLocals": false,
|
|
19
|
+
"noUnusedParameters": false,
|
|
20
|
+
"noImplicitReturns": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": false,
|
|
22
|
+
"emitDecoratorMetadata": true,
|
|
23
|
+
"experimentalDecorators": true,
|
|
24
|
+
"strictPropertyInitialization": false,
|
|
25
|
+
"moduleResolution": "node",
|
|
26
|
+
"baseUrl": "./",
|
|
27
|
+
"paths": {
|
|
28
|
+
"*": ["./node_modules/*"]
|
|
29
|
+
},
|
|
30
|
+
"typeRoots": ["./node_modules/@types"],
|
|
31
|
+
"skipLibCheck": true
|
|
32
|
+
},
|
|
33
|
+
"include": ["./"],
|
|
34
|
+
"exclude": ["./build", "./node_modules"]
|
|
35
|
+
}
|