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.
@@ -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
+ }