-
Notifications
You must be signed in to change notification settings - Fork 151
/
Copy pathdeploy-trigger.ts
130 lines (111 loc) · 3.54 KB
/
deploy-trigger.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import { S3 } from 'aws-sdk';
import unzipper from 'unzipper';
import {
lookup as mimeLookup,
contentType as mimeContentType,
} from 'mime-types';
import { deploymentConfigurationKey } from './constants';
import { generateRandomBuildId } from './utils';
import { FileResult } from './types';
// Metadata Key where the buildId is stored
const BuildIdMetaDataKey = 'x-amz-meta-tf-next-build-id';
/**
* Cache control header for immutable files that are stored in _next/static
*/
const CacheControlImmutable = 'public,max-age=31536000,immutable';
/**
* Static files that have no hashed filenames
*
* Must be refetched by the browser every time (max-age=0).
* But CloudFront CDN can hold the copy infinite time until a invalidation
* removes it (s-maxage=31536000).
* https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html#ExpirationDownloadDist
*/
const CacheControlStatic = 'public,max-age=0,must-revalidate,s-maxage=31536000';
interface Props {
s3: S3;
sourceBucket: string;
deployBucket: string;
key: string;
versionId?: string;
}
interface Response {
files: FileResult[];
buildId: string;
}
export async function deployTrigger({
s3,
key,
sourceBucket,
deployBucket,
versionId,
}: Props): Promise<Response> {
let buildId = '';
const params = {
Key: key,
Bucket: sourceBucket,
VersionId: versionId,
};
// Get the buildId from the metadata of the package
// If none is present, create a random id
const zipHeaders = await s3.headObject(params).promise();
if (zipHeaders.Metadata && BuildIdMetaDataKey in zipHeaders.Metadata) {
buildId = zipHeaders.Metadata[BuildIdMetaDataKey];
} else if (zipHeaders.ETag) {
// Fallback 1: If no metadata is present, use the etag
buildId = zipHeaders.ETag;
} else {
// Fallback 2: If no metadata or etag is present, create random id
buildId = generateRandomBuildId();
}
// Get the object that triggered the event
const zip = s3
.getObject(params)
.createReadStream()
.pipe(unzipper.Parse({ forceStream: true }));
const uploads: Promise<S3.ManagedUpload.SendData>[] = [];
for await (const e of zip) {
const entry = e as unzipper.Entry;
const fileName = entry.path;
const type = entry.type;
if (type === 'File') {
// Get ContentType
// Static pre-rendered pages have no file extension,
// files without extension get HTML mime type as fallback
const mimeType = mimeLookup(fileName);
const contentType =
typeof mimeType === 'string' ? mimeContentType(mimeType) : false;
// When the file is static (served from /_next/*) then it has immutable
// client - side caching).
// Otherwise it is only immutable on the CDN
const cacheControl = fileName.startsWith('_next/')
? CacheControlImmutable
: CacheControlStatic;
const uploadParams: S3.Types.PutObjectRequest = {
Bucket: deployBucket,
Key: fileName,
Body: entry,
ContentType:
typeof contentType === 'string'
? contentType
: 'text/html; charset=utf-8',
CacheControl: cacheControl,
};
// Sorry, but you cannot override the manifest
if (fileName !== deploymentConfigurationKey) {
uploads.push(s3.upload(uploadParams).promise());
}
} else {
entry.autodrain();
}
}
const files = (await Promise.all(uploads)).map((obj) => {
return { key: obj.Key, eTag: obj.ETag };
});
// Cleanup
await s3.deleteObject(params).promise();
return {
files,
buildId,
};
}