sst 2.29.0 → 2.30.1
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/cdk/deploy-stack.d.ts +4 -4
- package/cdk/deploy-stack.js +6 -5
- package/cdk/deployments-wrapper.js +7 -9
- package/cdk/deployments.d.ts +15 -8
- package/cdk/deployments.js +30 -42
- package/cli/commands/bind.js +0 -3
- package/cli/commands/types.js +12 -2
- package/cli/sst.js +0 -2
- package/constructs/BaseSite.d.ts +12 -3
- package/constructs/Distribution.d.ts +1 -1
- package/constructs/Distribution.js +2 -2
- package/constructs/NextjsSite.d.ts +2 -0
- package/constructs/NextjsSite.js +25 -14
- package/constructs/Script.d.ts +7 -5
- package/constructs/SsrSite.d.ts +97 -8
- package/constructs/SsrSite.js +208 -61
- package/constructs/StaticSite.d.ts +7 -9
- package/constructs/StaticSite.js +20 -15
- package/package.json +3 -3
- package/runtime/handlers.js +21 -10
- package/stacks/build.js +7 -1
- package/stacks/synth.js +62 -54
- package/util/semaphore.d.ts +7 -0
- package/util/semaphore.js +27 -0
package/constructs/SsrSite.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
2
|
import { Bucket, BucketProps, IBucket } from "aws-cdk-lib/aws-s3";
|
|
3
3
|
import { Function as CdkFunction, FunctionProps as CdkFunctionProps } from "aws-cdk-lib/aws-lambda";
|
|
4
|
-
import { ICachePolicy, IResponseHeadersPolicy, AllowedMethods, ErrorResponse } from "aws-cdk-lib/aws-cloudfront";
|
|
4
|
+
import { ICachePolicy, IResponseHeadersPolicy, AllowedMethods, CachePolicyProps, ErrorResponse } from "aws-cdk-lib/aws-cloudfront";
|
|
5
5
|
import { Schedule } from "aws-cdk-lib/aws-events";
|
|
6
6
|
import { DistributionDomainProps } from "./Distribution.js";
|
|
7
7
|
import { SSTConstruct } from "./Construct.js";
|
|
8
8
|
import { NodeJSProps, FunctionProps } from "./Function.js";
|
|
9
9
|
import { SsrFunction, SsrFunctionProps } from "./SsrFunction.js";
|
|
10
10
|
import { EdgeFunction, EdgeFunctionProps } from "./EdgeFunction.js";
|
|
11
|
-
import { BaseSiteFileOptions, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
|
|
11
|
+
import { BaseSiteFileOptions, BaseSiteFileOptionsDeprecated, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
|
|
12
12
|
import { Size } from "./util/size.js";
|
|
13
13
|
import { Duration } from "./util/duration.js";
|
|
14
14
|
import { Permissions } from "./util/permission.js";
|
|
@@ -54,6 +54,8 @@ export interface SsrDomainProps extends DistributionDomainProps {
|
|
|
54
54
|
}
|
|
55
55
|
export interface SsrSiteFileOptions extends BaseSiteFileOptions {
|
|
56
56
|
}
|
|
57
|
+
export interface SsrSiteFileOptionsDeprecated extends BaseSiteFileOptionsDeprecated {
|
|
58
|
+
}
|
|
57
59
|
export interface SsrSiteReplaceProps extends BaseSiteReplaceProps {
|
|
58
60
|
}
|
|
59
61
|
export interface SsrCdkDistributionProps extends BaseSiteCdkDistributionProps {
|
|
@@ -202,6 +204,93 @@ export interface SsrSiteProps {
|
|
|
202
204
|
*/
|
|
203
205
|
url?: string;
|
|
204
206
|
};
|
|
207
|
+
cache?: {
|
|
208
|
+
/**
|
|
209
|
+
* Character encoding for text based assets stored in the S3 cache (ex: html, css, js, etc.). If "none" is specified, no charset will be returned in header.
|
|
210
|
+
* @default utf-8
|
|
211
|
+
* @example
|
|
212
|
+
* ```js
|
|
213
|
+
* cache: {
|
|
214
|
+
* textEncoding: "iso-8859-1"
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
textEncoding?: "UTF-8" | "ISO-8859-1" | "Windows-1252" | "ASCII" | "none";
|
|
219
|
+
/**
|
|
220
|
+
* The strategy to use for invalidating the CDN cache. By default, the CDN cache will invalidate on changes any cached file, but this could become slow on very large projects.
|
|
221
|
+
* - "never" - No invalidation will be performed.
|
|
222
|
+
* - "all" - All files will be invalidated when any file changes. (Default, requires checking file content which will increase deployment time)
|
|
223
|
+
* - "versioned" - Only versioned files will be invalidated when versioned files change.
|
|
224
|
+
* - "always" - All files are invalidated on every deployment.
|
|
225
|
+
* @default all
|
|
226
|
+
* @example
|
|
227
|
+
* ```js
|
|
228
|
+
* cache: {
|
|
229
|
+
* cdnInvalidationStrategy: "versioned"
|
|
230
|
+
* }
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
cdnInvalidationStrategy?: "never" | "all" | "versioned" | "always";
|
|
234
|
+
/**
|
|
235
|
+
* The TTL for versioned files (ex: `main-1234.css`) in the CDN and browser cache. Ignored when `versionedFilesCacheHeader` is specified.
|
|
236
|
+
* @default 1 year
|
|
237
|
+
* @example
|
|
238
|
+
* ```js
|
|
239
|
+
* cache: {
|
|
240
|
+
* versionedFilesTTL: "30 days"
|
|
241
|
+
* }
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
versionedFilesTTL?: number | Duration;
|
|
245
|
+
/**
|
|
246
|
+
* The header to use for versioned files (ex: `main-1234.css`) in the CDN cache. When specified, the `versionedFilesTTL` option is ignored.
|
|
247
|
+
* @default public,max-age=31536000,immutable
|
|
248
|
+
* @example
|
|
249
|
+
* ```js
|
|
250
|
+
* cache: {
|
|
251
|
+
* versionedFilesCacheHeader: "public,max-age=31536000,immutable"
|
|
252
|
+
* }
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
versionedFilesCacheHeader?: string;
|
|
256
|
+
/**
|
|
257
|
+
* The TTL for non-versioned files (ex: `index.html`) in the CDN cache. Ignored when `nonVersionedFilesCacheHeader` is specified.
|
|
258
|
+
* @default 1 day
|
|
259
|
+
* @example
|
|
260
|
+
* ```js
|
|
261
|
+
* cache: {
|
|
262
|
+
* nonVersionedFilesTTL: "4 hours"
|
|
263
|
+
* }
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
nonVersionedFilesTTL?: number | Duration;
|
|
267
|
+
/**
|
|
268
|
+
* The header to use for non-versioned files (ex: `index.html`) in the CDN cache. When specified, the `nonVersionedFilesTTL` option is ignored.
|
|
269
|
+
* @default public,max-age=0,s-maxage=86400,stale-while-revalidate=8640
|
|
270
|
+
* @example
|
|
271
|
+
* ```js
|
|
272
|
+
* cache: {
|
|
273
|
+
* nonVersionedFilesCacheHeader: "public,max-age=0,no-cache"
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
nonVersionedFilesCacheHeader?: string;
|
|
278
|
+
/**
|
|
279
|
+
* List of file options to specify cache control and content type for cached files. These file options are appended to the default file options so it's possible to override the default file options by specifying an overlapping file pattern.
|
|
280
|
+
* @example
|
|
281
|
+
* ```js
|
|
282
|
+
* cache: {
|
|
283
|
+
* fileOptions: [
|
|
284
|
+
* {
|
|
285
|
+
* filters: [ { exclude: "*" }, { include: "*.zip" } ],
|
|
286
|
+
* cacheControl: "private,no-cache,no-store,must-revalidate",
|
|
287
|
+
* contentType: "application/zip",
|
|
288
|
+
* },
|
|
289
|
+
* ],
|
|
290
|
+
* }
|
|
291
|
+
*/
|
|
292
|
+
fileOptions?: SsrSiteFileOptions[];
|
|
293
|
+
};
|
|
205
294
|
/**
|
|
206
295
|
* While deploying, SST waits for the CloudFront cache invalidation process to finish. This ensures that the new content will be served once the deploy command finishes. However, this process can sometimes take more than 5 mins. For non-prod environments it might make sense to pass in `false`. That'll skip waiting for the cache to invalidate and speed up the deploy process.
|
|
207
296
|
* @default false
|
|
@@ -251,11 +340,11 @@ export interface SsrSiteProps {
|
|
|
251
340
|
server?: Pick<CdkFunctionProps, "layers" | "vpc" | "vpcSubnets" | "securityGroups" | "allowAllOutbound" | "allowPublicSubnet" | "architecture" | "logRetention"> & Pick<FunctionProps, "copyFiles">;
|
|
252
341
|
};
|
|
253
342
|
/**
|
|
254
|
-
* Pass in a list of file options to customize cache control and content type specific files.
|
|
255
|
-
*
|
|
343
|
+
* Pass in a list of file options to customize cache control and content type specific files. Specifying file options will bypass all default file options and only use the ones specified. Most configurations within the `cache` prop will be ignored.
|
|
344
|
+
* @deprecated Use `cache.fileOptions` instead. Note that the `cache.fileOptions` are appended to default file options, not a replacement as this prop was.
|
|
256
345
|
* @default
|
|
257
|
-
* Versioned files cached for 1 year at the CDN and
|
|
258
|
-
*
|
|
346
|
+
* Versioned files cached for 1 year at the CDN and browser level.
|
|
347
|
+
* Nonversioned files cached for 1 day at the CDN level, but not at the browser level.
|
|
259
348
|
* ```js
|
|
260
349
|
* fileOptions: [
|
|
261
350
|
* {
|
|
@@ -301,9 +390,8 @@ export interface SsrSiteProps {
|
|
|
301
390
|
* cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
|
|
302
391
|
* },
|
|
303
392
|
* ]
|
|
304
|
-
* ```
|
|
305
393
|
*/
|
|
306
|
-
fileOptions?:
|
|
394
|
+
fileOptions?: SsrSiteFileOptionsDeprecated[];
|
|
307
395
|
}
|
|
308
396
|
export type SsrSiteNormalizedProps = SsrSiteProps & {
|
|
309
397
|
path: Exclude<SsrSiteProps["path"], undefined>;
|
|
@@ -333,6 +421,7 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
333
421
|
private serverFunctionForDev?;
|
|
334
422
|
private distribution;
|
|
335
423
|
constructor(scope: Construct, id: string, rawProps?: SsrSiteProps);
|
|
424
|
+
protected static buildDefaultServerCachePolicyProps(allowedHeaders: string[]): CachePolicyProps;
|
|
336
425
|
/**
|
|
337
426
|
* The CloudFront URL of the website.
|
|
338
427
|
*/
|
package/constructs/SsrSite.js
CHANGED
|
@@ -67,7 +67,7 @@ export class SsrSite extends Construct {
|
|
|
67
67
|
const app = scope.node.root;
|
|
68
68
|
const stack = Stack.of(this);
|
|
69
69
|
const self = this;
|
|
70
|
-
const { path: sitePath, typesPath, buildCommand, runtime, timeout, memorySize, edge, regional, dev, nodejs, permissions, environment, bind, customDomain, waitForInvalidation, fileOptions, warm, cdk, } = props;
|
|
70
|
+
const { path: sitePath, typesPath, buildCommand, runtime, timeout, memorySize, edge, regional, dev, cache, nodejs, permissions, environment, bind, customDomain, waitForInvalidation, fileOptions, warm, cdk, } = props;
|
|
71
71
|
this.doNotDeploy = !stack.isActive || (app.mode === "dev" && !dev?.deploy);
|
|
72
72
|
validateSiteExists();
|
|
73
73
|
validateTimeout();
|
|
@@ -96,7 +96,7 @@ export class SsrSite extends Construct {
|
|
|
96
96
|
const edgeFunctions = createEdgeFunctions();
|
|
97
97
|
const origins = createOrigins();
|
|
98
98
|
const distribution = createCloudFrontDistribution();
|
|
99
|
-
|
|
99
|
+
createDistributionInvalidation();
|
|
100
100
|
// Create Warmer
|
|
101
101
|
createWarmer();
|
|
102
102
|
this.bucket = bucket;
|
|
@@ -411,7 +411,7 @@ function handler(event) {
|
|
|
411
411
|
originPath: "/" + (props.originPath ?? ""),
|
|
412
412
|
});
|
|
413
413
|
const assets = createS3OriginAssets(props.copy);
|
|
414
|
-
const assetFileOptions = fileOptions || createS3OriginAssetFileOptions(props.copy);
|
|
414
|
+
const assetFileOptions = fileOptions || createS3OriginAssetFileOptions(props.copy, cache);
|
|
415
415
|
const s3deployCR = createS3OriginDeployment(assets, assetFileOptions);
|
|
416
416
|
s3DeployCRs.push(s3deployCR);
|
|
417
417
|
return s3Origin;
|
|
@@ -562,27 +562,122 @@ function handler(event) {
|
|
|
562
562
|
}
|
|
563
563
|
return assets;
|
|
564
564
|
}
|
|
565
|
-
function createS3OriginAssetFileOptions(copy) {
|
|
565
|
+
function createS3OriginAssetFileOptions(copy, cache) {
|
|
566
566
|
const fileOptions = [];
|
|
567
|
-
|
|
567
|
+
const textEncoding = cache?.textEncoding ?? "UTF-8";
|
|
568
|
+
const nonVersionedFilesTTL = typeof cache?.nonVersionedFilesTTL === "number"
|
|
569
|
+
? cache.nonVersionedFilesTTL
|
|
570
|
+
: toCdkDuration(cache?.nonVersionedFilesTTL ?? "1 day").toSeconds();
|
|
571
|
+
const staleWhileRevalidateTTL = Math.max(Math.floor(nonVersionedFilesTTL / 10), 30);
|
|
572
|
+
const nonVersionedFilesCacheHeader = cache?.nonVersionedFilesCacheHeader ??
|
|
573
|
+
`public,max-age=0,s-maxage=${nonVersionedFilesTTL},stale-while-revalidate=${staleWhileRevalidateTTL}`;
|
|
574
|
+
const versionedFilesTTL = typeof cache?.versionedFilesTTL === "number"
|
|
575
|
+
? cache.versionedFilesTTL
|
|
576
|
+
: toCdkDuration(cache?.versionedFilesTTL ?? "365 days").toSeconds();
|
|
577
|
+
const versionedFilesCacheHeader = cache?.versionedFilesCacheHeader ??
|
|
578
|
+
`public,max-age=${versionedFilesTTL},immutable`;
|
|
579
|
+
const commonWebFileExtensions = {
|
|
580
|
+
txt: { mime: "text/plain", isText: true },
|
|
581
|
+
htm: { mime: "text/html", isText: true },
|
|
582
|
+
html: { mime: "text/html", isText: true },
|
|
583
|
+
xhtml: { mime: "application/xhtml+xml", isText: true },
|
|
584
|
+
css: { mime: "text/css", isText: true },
|
|
585
|
+
js: { mime: "text/javascript", isText: true },
|
|
586
|
+
mjs: { mime: "text/javascript", isText: true },
|
|
587
|
+
apng: { mime: "image/apng", isText: false },
|
|
588
|
+
avif: { mime: "image/avif", isText: false },
|
|
589
|
+
gif: { mime: "image/gif", isText: false },
|
|
590
|
+
jpeg: { mime: "image/jpeg", isText: false },
|
|
591
|
+
jpg: { mime: "image/jpeg", isText: false },
|
|
592
|
+
png: { mime: "image/png", isText: false },
|
|
593
|
+
svg: { mime: "image/svg+xml", isText: true },
|
|
594
|
+
bmp: { mime: "image/bmp", isText: false },
|
|
595
|
+
tiff: { mime: "image/tiff", isText: false },
|
|
596
|
+
webp: { mime: "image/webp", isText: false },
|
|
597
|
+
ico: { mime: "image/vnd.microsoft.icon", isText: false },
|
|
598
|
+
eot: { mime: "application/vnd.ms-fontobject", isText: false },
|
|
599
|
+
ttf: { mime: "font/ttf", isText: false },
|
|
600
|
+
otf: { mime: "font/otf", isText: false },
|
|
601
|
+
woff: { mime: "font/woff", isText: false },
|
|
602
|
+
woff2: { mime: "font/woff2", isText: false },
|
|
603
|
+
json: { mime: "application/json", isText: true },
|
|
604
|
+
jsonld: { mime: "application/ld+json", isText: true },
|
|
605
|
+
xml: { mime: "application/xml", isText: true },
|
|
606
|
+
pdf: { mime: "application/pdf", isText: false },
|
|
607
|
+
zip: { mime: "application/zip", isText: false },
|
|
608
|
+
};
|
|
609
|
+
copy.forEach((files) => {
|
|
568
610
|
if (!files.cached)
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
611
|
+
return;
|
|
612
|
+
for (const [extension, contentType] of Object.entries(commonWebFileExtensions)) {
|
|
613
|
+
// Create a file option for: common extension + unversioned files
|
|
614
|
+
fileOptions.push({
|
|
615
|
+
filters: [
|
|
616
|
+
{ exclude: "*" },
|
|
617
|
+
{ include: `${path.posix.join(files.to, "*")}.${extension}` },
|
|
618
|
+
...(files.versionedSubDir
|
|
619
|
+
? [
|
|
620
|
+
{
|
|
621
|
+
exclude: path.posix.join(files.to, files.versionedSubDir, "*"),
|
|
622
|
+
},
|
|
623
|
+
]
|
|
624
|
+
: []),
|
|
625
|
+
],
|
|
626
|
+
cacheControl: nonVersionedFilesCacheHeader,
|
|
627
|
+
contentType: `${contentType.mime}${contentType.isText && textEncoding !== "none"
|
|
628
|
+
? `;charset=${textEncoding}`
|
|
629
|
+
: ""}`,
|
|
630
|
+
});
|
|
631
|
+
// Create a file option for: common extension + versioned files
|
|
632
|
+
if (files.versionedSubDir) {
|
|
633
|
+
fileOptions.push({
|
|
634
|
+
filters: [
|
|
635
|
+
{ exclude: "*" },
|
|
636
|
+
{
|
|
637
|
+
include: path.posix.join(files.to, files.versionedSubDir, `*.${extension}`),
|
|
638
|
+
},
|
|
639
|
+
],
|
|
640
|
+
cacheControl: versionedFilesCacheHeader,
|
|
641
|
+
contentType: `${contentType.mime}${contentType.isText && textEncoding !== "none"
|
|
642
|
+
? `;charset=${textEncoding}`
|
|
643
|
+
: ""}`,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Create a file option for: other extensions + unversioned files
|
|
648
|
+
fileOptions.push({
|
|
649
|
+
filters: [
|
|
650
|
+
{ include: "*" },
|
|
651
|
+
...(files.versionedSubDir
|
|
652
|
+
? [
|
|
653
|
+
{
|
|
654
|
+
exclude: path.posix.join(files.to, files.versionedSubDir, "*"),
|
|
655
|
+
},
|
|
656
|
+
]
|
|
657
|
+
: []),
|
|
658
|
+
...Object.entries(commonWebFileExtensions).map(([ext]) => ({
|
|
659
|
+
exclude: `*.${ext}`,
|
|
660
|
+
})),
|
|
661
|
+
],
|
|
662
|
+
cacheControl: nonVersionedFilesCacheHeader,
|
|
663
|
+
});
|
|
664
|
+
// Create a file option for: other extensions + versioned files
|
|
665
|
+
if (files.versionedSubDir) {
|
|
574
666
|
fileOptions.push({
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
667
|
+
filters: [
|
|
668
|
+
{
|
|
669
|
+
include: path.posix.join(files.to, files.versionedSubDir, "*"),
|
|
670
|
+
},
|
|
671
|
+
...Object.entries(commonWebFileExtensions).map(([ext]) => ({
|
|
672
|
+
exclude: `*.${ext}`,
|
|
673
|
+
})),
|
|
674
|
+
],
|
|
675
|
+
cacheControl: versionedFilesCacheHeader,
|
|
584
676
|
});
|
|
585
677
|
}
|
|
678
|
+
});
|
|
679
|
+
if (cache?.fileOptions) {
|
|
680
|
+
fileOptions.push(...cache.fileOptions);
|
|
586
681
|
}
|
|
587
682
|
return fileOptions;
|
|
588
683
|
}
|
|
@@ -622,18 +717,25 @@ function handler(event) {
|
|
|
622
717
|
ObjectKey: asset.s3ObjectKey,
|
|
623
718
|
})),
|
|
624
719
|
DestinationBucketName: bucket.bucketName,
|
|
625
|
-
FileOptions: (fileOptions || []).map((
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
if (typeof include === "string") {
|
|
630
|
-
include = [include];
|
|
631
|
-
}
|
|
720
|
+
FileOptions: (fileOptions || []).map((o) => {
|
|
721
|
+
const { filters, cacheControl, contentType, contentEncoding } = o;
|
|
722
|
+
const { include, exclude } = o;
|
|
632
723
|
return [
|
|
633
|
-
...
|
|
634
|
-
|
|
635
|
-
|
|
724
|
+
...(typeof exclude === "string" ? [exclude] : exclude ?? [])
|
|
725
|
+
.map((entry) => ["--exclude", entry])
|
|
726
|
+
.flat(2),
|
|
727
|
+
...(typeof include === "string" ? [include] : include ?? [])
|
|
728
|
+
.map((entry) => ["--include", entry])
|
|
729
|
+
.flat(2),
|
|
730
|
+
...(filters || [])
|
|
731
|
+
.map((filter) => Object.entries(filter).map(([key, value]) => [
|
|
732
|
+
`--${key}`,
|
|
733
|
+
value,
|
|
734
|
+
]))
|
|
735
|
+
.flat(2),
|
|
736
|
+
cacheControl ? ["--cache-control", cacheControl] : [],
|
|
636
737
|
contentType ? ["--content-type", contentType] : [],
|
|
738
|
+
contentEncoding ? ["--content-encoding", contentEncoding] : [],
|
|
637
739
|
].flat();
|
|
638
740
|
}),
|
|
639
741
|
ReplaceValues: getS3ContentReplaceValues(),
|
|
@@ -656,19 +758,7 @@ function handler(event) {
|
|
|
656
758
|
const allowedHeaders = plan.cachePolicyAllowedHeaders || [];
|
|
657
759
|
singletonCachePolicy =
|
|
658
760
|
singletonCachePolicy ??
|
|
659
|
-
new CachePolicy(self, "ServerCache",
|
|
660
|
-
queryStringBehavior: CacheQueryStringBehavior.all(),
|
|
661
|
-
headerBehavior: allowedHeaders.length > 0
|
|
662
|
-
? CacheHeaderBehavior.allowList(...allowedHeaders)
|
|
663
|
-
: CacheHeaderBehavior.none(),
|
|
664
|
-
cookieBehavior: CacheCookieBehavior.none(),
|
|
665
|
-
defaultTtl: CdkDuration.days(0),
|
|
666
|
-
maxTtl: CdkDuration.days(365),
|
|
667
|
-
minTtl: CdkDuration.days(0),
|
|
668
|
-
enableAcceptEncodingBrotli: true,
|
|
669
|
-
enableAcceptEncodingGzip: true,
|
|
670
|
-
comment: "SST server response cache policy",
|
|
671
|
-
});
|
|
761
|
+
new CachePolicy(self, "ServerCache", SsrSite.buildDefaultServerCachePolicyProps(allowedHeaders));
|
|
672
762
|
return singletonCachePolicy;
|
|
673
763
|
}
|
|
674
764
|
function useServerBehaviorOriginRequestPolicy() {
|
|
@@ -705,34 +795,91 @@ function handler(event) {
|
|
|
705
795
|
});
|
|
706
796
|
return replaceValues;
|
|
707
797
|
}
|
|
708
|
-
function
|
|
798
|
+
function createDistributionInvalidation() {
|
|
799
|
+
const cdnInvalidationStrategy = cache?.cdnInvalidationStrategy ?? "all";
|
|
800
|
+
if (cdnInvalidationStrategy === "never")
|
|
801
|
+
return;
|
|
802
|
+
if (plan.buildId) {
|
|
803
|
+
distribution.createInvalidation(plan.buildId);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (cdnInvalidationStrategy === "always") {
|
|
807
|
+
const buildId = Date.now().toString(16) + Math.random().toString(16).slice(2);
|
|
808
|
+
distribution.createInvalidation(buildId);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
709
811
|
// We will generate a hash based on the contents of the S3 files with cache enabled.
|
|
710
812
|
// This will be used to determine if we need to invalidate our CloudFront cache.
|
|
711
813
|
const s3Origin = Object.values(plan.origins).find((origin) => origin.type === "s3");
|
|
712
814
|
if (s3Origin?.type !== "s3")
|
|
713
|
-
return
|
|
714
|
-
const cachedS3Files = s3Origin.copy.
|
|
715
|
-
if (
|
|
716
|
-
return
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
};
|
|
726
|
-
const files = glob.sync("**", globOptions);
|
|
727
|
-
const hash = crypto.createHash("sha1");
|
|
728
|
-
for (const file of files) {
|
|
729
|
-
hash.update(file);
|
|
815
|
+
return;
|
|
816
|
+
const cachedS3Files = s3Origin.copy.filter((file) => file.cached);
|
|
817
|
+
if (cachedS3Files.length === 0)
|
|
818
|
+
return;
|
|
819
|
+
// Build invalidation paths
|
|
820
|
+
const invalidationPaths = [];
|
|
821
|
+
if (cdnInvalidationStrategy === "versioned") {
|
|
822
|
+
cachedS3Files.forEach((item) => {
|
|
823
|
+
if (!item.versionedSubDir)
|
|
824
|
+
return;
|
|
825
|
+
invalidationPaths.push(path.posix.join("/", item.to, item.versionedSubDir, "*"));
|
|
826
|
+
});
|
|
730
827
|
}
|
|
828
|
+
if (invalidationPaths.length === 0)
|
|
829
|
+
invalidationPaths.push("/*");
|
|
830
|
+
// Build build ID
|
|
831
|
+
const hash = crypto.createHash("md5");
|
|
832
|
+
cachedS3Files.forEach((item) => {
|
|
833
|
+
// The below options are needed to support following symlinks when building zip files:
|
|
834
|
+
// - nodir: This will prevent symlinks themselves from being copied into the zip.
|
|
835
|
+
// - follow: This will follow symlinks and copy the files within.
|
|
836
|
+
const globOptions = {
|
|
837
|
+
dot: true,
|
|
838
|
+
nodir: true,
|
|
839
|
+
follow: true,
|
|
840
|
+
cwd: path.resolve(sitePath, item.from),
|
|
841
|
+
};
|
|
842
|
+
// For versioned files, use file path for digest since file version in name should change on content change
|
|
843
|
+
if (item.versionedSubDir) {
|
|
844
|
+
glob
|
|
845
|
+
.sync("**", {
|
|
846
|
+
...globOptions,
|
|
847
|
+
cwd: path.resolve(sitePath, item.from, item.versionedSubDir),
|
|
848
|
+
})
|
|
849
|
+
.forEach((filePath) => hash.update(filePath));
|
|
850
|
+
}
|
|
851
|
+
// For non-versioned files, use file content for digest
|
|
852
|
+
if (cdnInvalidationStrategy === "all") {
|
|
853
|
+
glob
|
|
854
|
+
.sync("**", {
|
|
855
|
+
...globOptions,
|
|
856
|
+
ignore: item.versionedSubDir
|
|
857
|
+
? [path.posix.join(item.versionedSubDir, "**")]
|
|
858
|
+
: undefined,
|
|
859
|
+
})
|
|
860
|
+
.forEach((filePath) => hash.update(fs.readFileSync(path.resolve(sitePath, item.from, filePath))));
|
|
861
|
+
}
|
|
862
|
+
});
|
|
731
863
|
const buildId = hash.digest("hex");
|
|
732
864
|
Logger.debug(`Generated build ID ${buildId}`);
|
|
733
|
-
|
|
865
|
+
distribution.createInvalidation(buildId, invalidationPaths);
|
|
734
866
|
}
|
|
735
867
|
}
|
|
868
|
+
static buildDefaultServerCachePolicyProps(allowedHeaders) {
|
|
869
|
+
return {
|
|
870
|
+
queryStringBehavior: CacheQueryStringBehavior.all(),
|
|
871
|
+
headerBehavior: allowedHeaders.length > 0
|
|
872
|
+
? CacheHeaderBehavior.allowList(...allowedHeaders)
|
|
873
|
+
: CacheHeaderBehavior.none(),
|
|
874
|
+
cookieBehavior: CacheCookieBehavior.none(),
|
|
875
|
+
defaultTtl: CdkDuration.days(0),
|
|
876
|
+
maxTtl: CdkDuration.days(365),
|
|
877
|
+
minTtl: CdkDuration.days(0),
|
|
878
|
+
enableAcceptEncodingBrotli: true,
|
|
879
|
+
enableAcceptEncodingGzip: true,
|
|
880
|
+
comment: "SST server response cache policy",
|
|
881
|
+
};
|
|
882
|
+
}
|
|
736
883
|
/**
|
|
737
884
|
* The CloudFront URL of the website.
|
|
738
885
|
*/
|
|
@@ -2,7 +2,7 @@ import { Construct } from "constructs";
|
|
|
2
2
|
import { Bucket, BucketProps, IBucket } from "aws-cdk-lib/aws-s3";
|
|
3
3
|
import { IDistribution } from "aws-cdk-lib/aws-cloudfront";
|
|
4
4
|
import { DistributionDomainProps } from "./Distribution.js";
|
|
5
|
-
import { BaseSiteFileOptions, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
|
|
5
|
+
import { BaseSiteFileOptions, BaseSiteReplaceProps, BaseSiteCdkDistributionProps, BaseSiteFileOptionsDeprecated } from "./BaseSite.js";
|
|
6
6
|
import { SSTConstruct } from "./Construct.js";
|
|
7
7
|
import { FunctionBindingProps } from "./util/functionBinding.js";
|
|
8
8
|
export interface StaticSiteProps {
|
|
@@ -69,18 +69,15 @@ export interface StaticSiteProps {
|
|
|
69
69
|
buildOutput?: string;
|
|
70
70
|
/**
|
|
71
71
|
* Pass in a list of file options to configure cache control for different files. Behind the scenes, the `StaticSite` construct uses a combination of the `s3 cp` and `s3 sync` commands to upload the website content to the S3 bucket. An `s3 cp` command is run for each file option block, and the options are passed in as the command options.
|
|
72
|
-
*
|
|
73
72
|
* @default No cache control for HTML files, and a 1 year cache control for JS/CSS files.
|
|
74
73
|
* ```js
|
|
75
74
|
* [
|
|
76
75
|
* {
|
|
77
|
-
* exclude: "*",
|
|
78
|
-
* include: "*.html",
|
|
76
|
+
* filters: [{ exclude: "*" }, { include: "*.html" }],
|
|
79
77
|
* cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
|
|
80
78
|
* },
|
|
81
79
|
* {
|
|
82
|
-
* exclude: "*",
|
|
83
|
-
* include: ["*.js", "*.css"],
|
|
80
|
+
* filters: [{ exclude: "*" }, { include: "*.js" }, { include: "*.css" }],
|
|
84
81
|
* cacheControl: "max-age=31536000,public,immutable",
|
|
85
82
|
* },
|
|
86
83
|
* ]
|
|
@@ -90,14 +87,13 @@ export interface StaticSiteProps {
|
|
|
90
87
|
* new StaticSite(stack, "Site", {
|
|
91
88
|
* buildOutput: "dist",
|
|
92
89
|
* fileOptions: [{
|
|
93
|
-
* exclude: "*",
|
|
94
|
-
* include: "*.js",
|
|
90
|
+
* filters: [{ exclude: "*" }, { include: "*.js" }],
|
|
95
91
|
* cacheControl: "max-age=31536000,public,immutable",
|
|
96
92
|
* }]
|
|
97
93
|
* });
|
|
98
94
|
* ```
|
|
99
95
|
*/
|
|
100
|
-
fileOptions?: StaticSiteFileOptions[];
|
|
96
|
+
fileOptions?: StaticSiteFileOptions[] | StaticSiteFileOptionsDeprecated[];
|
|
101
97
|
/**
|
|
102
98
|
* Pass in a list of placeholder values to be replaced in the website content. For example, the follow configuration:
|
|
103
99
|
*
|
|
@@ -269,6 +265,8 @@ export interface StaticSiteDomainProps extends DistributionDomainProps {
|
|
|
269
265
|
}
|
|
270
266
|
export interface StaticSiteFileOptions extends BaseSiteFileOptions {
|
|
271
267
|
}
|
|
268
|
+
export interface StaticSiteFileOptionsDeprecated extends BaseSiteFileOptionsDeprecated {
|
|
269
|
+
}
|
|
272
270
|
export interface StaticSiteReplaceProps extends BaseSiteReplaceProps {
|
|
273
271
|
}
|
|
274
272
|
export interface StaticSiteCdkDistributionProps extends BaseSiteCdkDistributionProps {
|
package/constructs/StaticSite.js
CHANGED
|
@@ -296,15 +296,13 @@ interface ImportMeta {
|
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
298
|
createS3Deployment(cliLayer, assets, filenamesAsset) {
|
|
299
|
-
const fileOptions = this.props.fileOptions
|
|
299
|
+
const fileOptions = this.props.fileOptions ?? [
|
|
300
300
|
{
|
|
301
|
-
exclude: "*",
|
|
302
|
-
include: "*.html",
|
|
301
|
+
filters: [{ exclude: "*" }, { include: "*.html" }],
|
|
303
302
|
cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
|
|
304
303
|
},
|
|
305
304
|
{
|
|
306
|
-
exclude: "*",
|
|
307
|
-
include: ["*.js", "*.css"],
|
|
305
|
+
filters: [{ exclude: "*" }, { include: "*.js" }, { include: "*.css" }],
|
|
308
306
|
cacheControl: "max-age=31536000,public,immutable",
|
|
309
307
|
},
|
|
310
308
|
];
|
|
@@ -348,18 +346,25 @@ interface ImportMeta {
|
|
|
348
346
|
BucketName: filenamesAsset.s3BucketName,
|
|
349
347
|
ObjectKey: filenamesAsset.s3ObjectKey,
|
|
350
348
|
},
|
|
351
|
-
FileOptions: (fileOptions || []).map((
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
if (typeof include === "string") {
|
|
356
|
-
include = [include];
|
|
357
|
-
}
|
|
349
|
+
FileOptions: (fileOptions || []).map((o) => {
|
|
350
|
+
const { filters, cacheControl, contentType, contentEncoding } = o;
|
|
351
|
+
const { include, exclude } = o;
|
|
358
352
|
return [
|
|
359
|
-
...
|
|
360
|
-
|
|
361
|
-
|
|
353
|
+
...(typeof exclude === "string" ? [exclude] : exclude ?? [])
|
|
354
|
+
.map((entry) => ["--exclude", entry])
|
|
355
|
+
.flat(2),
|
|
356
|
+
...(typeof include === "string" ? [include] : include ?? [])
|
|
357
|
+
.map((entry) => ["--include", entry])
|
|
358
|
+
.flat(2),
|
|
359
|
+
...(filters || [])
|
|
360
|
+
.map((filter) => Object.entries(filter).map(([key, value]) => [
|
|
361
|
+
`--${key}`,
|
|
362
|
+
value,
|
|
363
|
+
]))
|
|
364
|
+
.flat(2),
|
|
365
|
+
cacheControl ? ["--cache-control", cacheControl] : [],
|
|
362
366
|
contentType ? ["--content-type", contentType] : [],
|
|
367
|
+
contentEncoding ? ["--content-encoding", contentEncoding] : [],
|
|
363
368
|
].flat();
|
|
364
369
|
}),
|
|
365
370
|
ReplaceValues: this.getS3ContentReplaceValues(),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sideEffects": false,
|
|
3
3
|
"name": "sst",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.30.1",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sst": "cli/sst.js"
|
|
7
7
|
},
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"ora": "^6.1.2",
|
|
88
88
|
"react": "18.2.0",
|
|
89
89
|
"remeda": "^1.3.0",
|
|
90
|
-
"sst-aws-cdk": "2.
|
|
90
|
+
"sst-aws-cdk": "2.95.1",
|
|
91
91
|
"tree-kill": "^1.2.2",
|
|
92
92
|
"undici": "^5.12.0",
|
|
93
93
|
"uuid": "^9.0.0",
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"@types/ws": "^8.5.3",
|
|
120
120
|
"@types/yargs": "^17.0.13",
|
|
121
121
|
"archiver": "^5.3.1",
|
|
122
|
-
"astro-sst": "2.
|
|
122
|
+
"astro-sst": "2.30.1",
|
|
123
123
|
"tsx": "^3.12.1",
|
|
124
124
|
"typescript": "^5.2.2",
|
|
125
125
|
"vitest": "^0.33.0"
|