sst 2.30.4 → 2.32.0
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/constructs/App.js +7 -7
- package/constructs/BaseSite.d.ts +2 -13
- package/constructs/Distribution.d.ts +5 -10
- package/constructs/Distribution.js +6 -4
- package/constructs/Function.d.ts +6 -6
- package/constructs/Function.js +3 -3
- package/constructs/NextjsSite.d.ts +14 -2
- package/constructs/NextjsSite.js +204 -39
- package/constructs/RDS.js +2 -2
- package/constructs/Service.d.ts +0 -1
- package/constructs/Service.js +0 -1
- package/constructs/SsrSite.d.ts +58 -88
- package/constructs/SsrSite.js +136 -238
- package/constructs/StaticSite.d.ts +46 -33
- package/constructs/StaticSite.js +60 -76
- package/constructs/deprecated/NextjsSite.js +57 -71
- package/package.json +5 -4
- package/support/base-site-archiver.mjs +18 -18
- package/support/custom-resources/index.mjs +14875 -6761
- package/support/base-site-custom-resource/s3-handler.py +0 -195
- package/support/base-site-custom-resource/s3-upload.py +0 -89
package/constructs/SsrSite.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import url from "url";
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
import
|
|
4
|
+
import { globSync } from "glob";
|
|
5
5
|
import crypto from "crypto";
|
|
6
6
|
import spawn from "cross-spawn";
|
|
7
7
|
import { execSync } from "child_process";
|
|
@@ -12,7 +12,6 @@ import { Effect, Role, Policy, PolicyStatement, AccountPrincipal, ServicePrincip
|
|
|
12
12
|
import { Function as CdkFunction, Code, Runtime, FunctionUrlAuthType, InvokeMode, } from "aws-cdk-lib/aws-lambda";
|
|
13
13
|
import { Asset } from "aws-cdk-lib/aws-s3-assets";
|
|
14
14
|
import { ViewerProtocolPolicy, AllowedMethods, CachedMethods, LambdaEdgeEventType, CachePolicy, CacheQueryStringBehavior, CacheHeaderBehavior, CacheCookieBehavior, OriginRequestPolicy, Function as CfFunction, FunctionCode as CfFunctionCode, FunctionEventType as CfFunctionEventType, } from "aws-cdk-lib/aws-cloudfront";
|
|
15
|
-
import { AwsCliLayer } from "aws-cdk-lib/lambda-layer-awscli";
|
|
16
15
|
import { S3Origin, HttpOrigin, OriginGroup, } from "aws-cdk-lib/aws-cloudfront-origins";
|
|
17
16
|
import { Rule, Schedule } from "aws-cdk-lib/aws-events";
|
|
18
17
|
import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
|
|
@@ -56,21 +55,26 @@ export class SsrSite extends Construct {
|
|
|
56
55
|
const props = {
|
|
57
56
|
path: ".",
|
|
58
57
|
typesPath: ".",
|
|
59
|
-
waitForInvalidation: false,
|
|
60
58
|
runtime: "nodejs18.x",
|
|
61
59
|
timeout: "10 seconds",
|
|
62
60
|
memorySize: "1024 MB",
|
|
63
61
|
...rawProps,
|
|
62
|
+
invalidation: {
|
|
63
|
+
wait: rawProps?.waitForInvalidation,
|
|
64
|
+
paths: "all",
|
|
65
|
+
...rawProps?.invalidation,
|
|
66
|
+
},
|
|
64
67
|
};
|
|
65
68
|
this.id = id;
|
|
66
69
|
this.props = props;
|
|
67
70
|
const app = scope.node.root;
|
|
68
71
|
const stack = Stack.of(this);
|
|
69
72
|
const self = this;
|
|
70
|
-
const { path: sitePath, typesPath, buildCommand, runtime, timeout, memorySize, edge, regional, dev,
|
|
73
|
+
const { path: sitePath, typesPath, buildCommand, runtime, timeout, memorySize, edge, regional, dev, assets, nodejs, permissions, environment, bind, customDomain, invalidation, warm, cdk, } = props;
|
|
71
74
|
this.doNotDeploy = !stack.isActive || (app.mode === "dev" && !dev?.deploy);
|
|
72
75
|
validateSiteExists();
|
|
73
76
|
validateTimeout();
|
|
77
|
+
validateDeprecatedFileOptions();
|
|
74
78
|
writeTypesFile(typesPath);
|
|
75
79
|
useSites().add(stack.stackName, id, this.constructor.name, props);
|
|
76
80
|
if (this.doNotDeploy) {
|
|
@@ -82,7 +86,6 @@ export class SsrSite extends Construct {
|
|
|
82
86
|
}
|
|
83
87
|
let s3DeployCRs = [];
|
|
84
88
|
let ssrFunctions = [];
|
|
85
|
-
let singletonAwsCliLayer;
|
|
86
89
|
let singletonUrlSigner;
|
|
87
90
|
let singletonCachePolicy;
|
|
88
91
|
let singletonOriginRequestPolicy;
|
|
@@ -110,7 +113,7 @@ export class SsrSite extends Construct {
|
|
|
110
113
|
app.registerTypes(this);
|
|
111
114
|
function validateSiteExists() {
|
|
112
115
|
if (!fs.existsSync(sitePath)) {
|
|
113
|
-
throw new
|
|
116
|
+
throw new VisibleError(`No site found at "${path.resolve(sitePath)}"`);
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
function validateTimeout() {
|
|
@@ -119,9 +122,15 @@ export class SsrSite extends Construct {
|
|
|
119
122
|
: toCdkDuration(timeout).toSeconds();
|
|
120
123
|
const limit = edge ? 30 : 180;
|
|
121
124
|
if (num > limit) {
|
|
122
|
-
throw new
|
|
123
|
-
? `
|
|
124
|
-
: `
|
|
125
|
+
throw new VisibleError(edge
|
|
126
|
+
? `In the "${id}" construct, timeout must be less than or equal to 30 seconds when the "edge" flag is enabled.`
|
|
127
|
+
: `In the "${id}" construct, timeout must be less than or equal to 180 seconds.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function validateDeprecatedFileOptions() {
|
|
131
|
+
// @ts-expect-error
|
|
132
|
+
if (props.fileOptions) {
|
|
133
|
+
throw new VisibleError(`In the "${id}" construct, the "fileOptions" property has been replaced by "assets.fileOptions". More details on upgrading - https://docs.sst.dev/upgrade-guide#upgrade-to-v2310`);
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
function writeTypesFile(typesPath) {
|
|
@@ -254,7 +263,6 @@ export class SsrSite extends Construct {
|
|
|
254
263
|
const distribution = new Distribution(self, "CDN", {
|
|
255
264
|
scopeOverride: self,
|
|
256
265
|
customDomain,
|
|
257
|
-
waitForInvalidation,
|
|
258
266
|
cdk: {
|
|
259
267
|
distribution: {
|
|
260
268
|
// these values can be overwritten
|
|
@@ -411,8 +419,7 @@ function handler(event) {
|
|
|
411
419
|
originPath: "/" + (props.originPath ?? ""),
|
|
412
420
|
});
|
|
413
421
|
const assets = createS3OriginAssets(props.copy);
|
|
414
|
-
const
|
|
415
|
-
const s3deployCR = createS3OriginDeployment(assets, assetFileOptions);
|
|
422
|
+
const s3deployCR = createS3OriginDeployment(props.copy, assets);
|
|
416
423
|
s3DeployCRs.push(s3deployCR);
|
|
417
424
|
return s3Origin;
|
|
418
425
|
}
|
|
@@ -562,185 +569,44 @@ function handler(event) {
|
|
|
562
569
|
}
|
|
563
570
|
return assets;
|
|
564
571
|
}
|
|
565
|
-
function
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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) => {
|
|
610
|
-
if (!files.cached)
|
|
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) {
|
|
666
|
-
fileOptions.push({
|
|
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,
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
});
|
|
679
|
-
if (cache?.fileOptions) {
|
|
680
|
-
fileOptions.push(...cache.fileOptions);
|
|
681
|
-
}
|
|
682
|
-
return fileOptions;
|
|
683
|
-
}
|
|
684
|
-
function createS3OriginDeployment(assets, fileOptions) {
|
|
685
|
-
// Create a Lambda function that will be doing the uploading
|
|
686
|
-
const uploader = new CdkFunction(self, "S3Uploader", {
|
|
687
|
-
code: Code.fromAsset(path.join(__dirname, "../support/base-site-custom-resource")),
|
|
688
|
-
layers: [useAwsCliLayer()],
|
|
689
|
-
runtime: Runtime.PYTHON_3_11,
|
|
690
|
-
handler: "s3-upload.handler",
|
|
691
|
-
timeout: CdkDuration.minutes(15),
|
|
692
|
-
memorySize: 1024,
|
|
693
|
-
});
|
|
694
|
-
bucket.grantReadWrite(uploader);
|
|
695
|
-
assets.forEach((asset) => asset.grantRead(uploader));
|
|
696
|
-
// Create the custom resource function
|
|
697
|
-
const handler = new CdkFunction(self, "S3Handler", {
|
|
698
|
-
code: Code.fromAsset(path.join(__dirname, "../support/base-site-custom-resource")),
|
|
699
|
-
layers: [useAwsCliLayer()],
|
|
700
|
-
runtime: Runtime.PYTHON_3_11,
|
|
701
|
-
handler: "s3-handler.handler",
|
|
702
|
-
timeout: CdkDuration.minutes(15),
|
|
703
|
-
memorySize: 1024,
|
|
704
|
-
environment: {
|
|
705
|
-
UPLOADER_FUNCTION_NAME: uploader.functionName,
|
|
706
|
-
},
|
|
572
|
+
function createS3OriginDeployment(copy, s3Assets) {
|
|
573
|
+
const policy = new Policy(self, "S3UploaderPolicy", {
|
|
574
|
+
statements: [
|
|
575
|
+
new PolicyStatement({
|
|
576
|
+
effect: Effect.ALLOW,
|
|
577
|
+
actions: ["lambda:InvokeFunction"],
|
|
578
|
+
resources: [stack.customResourceHandler.functionArn],
|
|
579
|
+
}),
|
|
580
|
+
new PolicyStatement({
|
|
581
|
+
effect: Effect.ALLOW,
|
|
582
|
+
actions: ["s3:ListBucket", "s3:PutObject", "s3:DeleteObject"],
|
|
583
|
+
resources: [bucket.bucketArn, `${bucket.bucketArn}/*`],
|
|
584
|
+
}),
|
|
585
|
+
new PolicyStatement({
|
|
586
|
+
effect: Effect.ALLOW,
|
|
587
|
+
actions: ["s3:GetObject"],
|
|
588
|
+
resources: [`${s3Assets[0].bucket.bucketArn}/*`],
|
|
589
|
+
}),
|
|
590
|
+
],
|
|
707
591
|
});
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
serviceToken: handler.functionArn,
|
|
713
|
-
resourceType: "Custom::SSTBucketDeployment",
|
|
592
|
+
stack.customResourceHandler.role?.attachInlinePolicy(policy);
|
|
593
|
+
const resource = new CustomResource(self, "S3Uploader", {
|
|
594
|
+
serviceToken: stack.customResourceHandler.functionArn,
|
|
595
|
+
resourceType: "Custom::S3Uploader",
|
|
714
596
|
properties: {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
597
|
+
sources: s3Assets.map((s3Asset) => ({
|
|
598
|
+
bucketName: s3Asset.s3BucketName,
|
|
599
|
+
objectKey: s3Asset.s3ObjectKey,
|
|
718
600
|
})),
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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] : [],
|
|
737
|
-
contentType ? ["--content-type", contentType] : [],
|
|
738
|
-
contentEncoding ? ["--content-encoding", contentEncoding] : [],
|
|
739
|
-
].flat();
|
|
740
|
-
}),
|
|
741
|
-
ReplaceValues: getS3ContentReplaceValues(),
|
|
601
|
+
destinationBucketName: bucket.bucketName,
|
|
602
|
+
concurrency: assets?._uploadConcurrency,
|
|
603
|
+
textEncoding: assets?.textEncoding ?? "utf-8",
|
|
604
|
+
fileOptions: getS3FileOptions(copy),
|
|
605
|
+
replaceValues: getS3ContentReplaceValues(),
|
|
742
606
|
},
|
|
743
607
|
});
|
|
608
|
+
resource.node.addDependency(policy);
|
|
609
|
+
return resource;
|
|
744
610
|
}
|
|
745
611
|
function useFunctionUrlSigningFunction() {
|
|
746
612
|
singletonUrlSigner =
|
|
@@ -768,10 +634,40 @@ function handler(event) {
|
|
|
768
634
|
OriginRequestPolicy.fromOriginRequestPolicyId(self, "ServerOriginRequestPolicy", "b689b0a8-53d0-40ab-baf2-68738e2966ac");
|
|
769
635
|
return singletonOriginRequestPolicy;
|
|
770
636
|
}
|
|
771
|
-
function
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
637
|
+
function getS3FileOptions(copy) {
|
|
638
|
+
const fileOptions = [];
|
|
639
|
+
const nonVersionedFilesTTL = typeof assets?.nonVersionedFilesTTL === "number"
|
|
640
|
+
? assets.nonVersionedFilesTTL
|
|
641
|
+
: toCdkDuration(assets?.nonVersionedFilesTTL ?? "1 day").toSeconds();
|
|
642
|
+
const staleWhileRevalidateTTL = Math.max(Math.floor(nonVersionedFilesTTL / 10), 30);
|
|
643
|
+
const versionedFilesTTL = typeof assets?.versionedFilesTTL === "number"
|
|
644
|
+
? assets.versionedFilesTTL
|
|
645
|
+
: toCdkDuration(assets?.versionedFilesTTL ?? "365 days").toSeconds();
|
|
646
|
+
copy.forEach(({ cached, to, versionedSubDir }) => {
|
|
647
|
+
if (!cached)
|
|
648
|
+
return;
|
|
649
|
+
// Create a default file option for: unversioned files
|
|
650
|
+
fileOptions.push({
|
|
651
|
+
files: "**",
|
|
652
|
+
ignore: versionedSubDir
|
|
653
|
+
? path.posix.join(to, versionedSubDir, "**")
|
|
654
|
+
: undefined,
|
|
655
|
+
cacheControl: assets?.nonVersionedFilesCacheHeader ??
|
|
656
|
+
`public,max-age=0,s-maxage=${nonVersionedFilesTTL},stale-while-revalidate=${staleWhileRevalidateTTL}`,
|
|
657
|
+
});
|
|
658
|
+
// Create a default file option for: versioned files
|
|
659
|
+
if (versionedSubDir) {
|
|
660
|
+
fileOptions.push({
|
|
661
|
+
files: path.posix.join(to, versionedSubDir, "**"),
|
|
662
|
+
cacheControl: assets?.versionedFilesCacheHeader ??
|
|
663
|
+
`public,max-age=${versionedFilesTTL},immutable`,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
if (assets?.fileOptions) {
|
|
668
|
+
fileOptions.push(...assets.fileOptions);
|
|
669
|
+
}
|
|
670
|
+
return fileOptions;
|
|
775
671
|
}
|
|
776
672
|
function getS3ContentReplaceValues() {
|
|
777
673
|
const replaceValues = [];
|
|
@@ -796,18 +692,7 @@ function handler(event) {
|
|
|
796
692
|
return replaceValues;
|
|
797
693
|
}
|
|
798
694
|
function createDistributionInvalidation() {
|
|
799
|
-
const
|
|
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
|
-
}
|
|
695
|
+
const paths = invalidation.paths;
|
|
811
696
|
// We will generate a hash based on the contents of the S3 files with cache enabled.
|
|
812
697
|
// This will be used to determine if we need to invalidate our CloudFront cache.
|
|
813
698
|
const s3Origin = Object.values(plan.origins).find((origin) => origin.type === "s3");
|
|
@@ -818,51 +703,64 @@ function handler(event) {
|
|
|
818
703
|
return;
|
|
819
704
|
// Build invalidation paths
|
|
820
705
|
const invalidationPaths = [];
|
|
821
|
-
if (
|
|
706
|
+
if (paths === "none") {
|
|
707
|
+
}
|
|
708
|
+
else if (paths === "all") {
|
|
709
|
+
invalidationPaths.push("/*");
|
|
710
|
+
}
|
|
711
|
+
else if (paths === "versioned") {
|
|
822
712
|
cachedS3Files.forEach((item) => {
|
|
823
713
|
if (!item.versionedSubDir)
|
|
824
714
|
return;
|
|
825
715
|
invalidationPaths.push(path.posix.join("/", item.to, item.versionedSubDir, "*"));
|
|
826
716
|
});
|
|
827
717
|
}
|
|
718
|
+
else {
|
|
719
|
+
invalidationPaths.push(...paths);
|
|
720
|
+
}
|
|
828
721
|
if (invalidationPaths.length === 0)
|
|
829
|
-
|
|
722
|
+
return;
|
|
830
723
|
// Build build ID
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
:
|
|
859
|
-
|
|
860
|
-
.forEach((filePath) => hash.update(fs.readFileSync(path.resolve(sitePath, item.from, filePath))));
|
|
861
|
-
|
|
724
|
+
let invalidationBuildId;
|
|
725
|
+
if (plan.buildId) {
|
|
726
|
+
invalidationBuildId = plan.buildId;
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
const hash = crypto.createHash("md5");
|
|
730
|
+
cachedS3Files.forEach((item) => {
|
|
731
|
+
// The below options are needed to support following symlinks when building zip files:
|
|
732
|
+
// - nodir: This will prevent symlinks themselves from being copied into the zip.
|
|
733
|
+
// - follow: This will follow symlinks and copy the files within.
|
|
734
|
+
// For versioned files, use file path for digest since file version in name should change on content change
|
|
735
|
+
if (item.versionedSubDir) {
|
|
736
|
+
globSync("**", {
|
|
737
|
+
dot: true,
|
|
738
|
+
nodir: true,
|
|
739
|
+
follow: true,
|
|
740
|
+
cwd: path.resolve(sitePath, item.from, item.versionedSubDir),
|
|
741
|
+
}).forEach((filePath) => hash.update(filePath));
|
|
742
|
+
}
|
|
743
|
+
// For non-versioned files, use file content for digest
|
|
744
|
+
if (paths !== "versioned") {
|
|
745
|
+
globSync("**", {
|
|
746
|
+
ignore: item.versionedSubDir
|
|
747
|
+
? [path.posix.join(item.versionedSubDir, "**")]
|
|
748
|
+
: undefined,
|
|
749
|
+
dot: true,
|
|
750
|
+
nodir: true,
|
|
751
|
+
follow: true,
|
|
752
|
+
cwd: path.resolve(sitePath, item.from),
|
|
753
|
+
}).forEach((filePath) => hash.update(fs.readFileSync(path.resolve(sitePath, item.from, filePath))));
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
invalidationBuildId = hash.digest("hex");
|
|
757
|
+
Logger.debug(`Generated build ID ${invalidationBuildId}`);
|
|
758
|
+
}
|
|
759
|
+
distribution.createInvalidation({
|
|
760
|
+
version: invalidationBuildId,
|
|
761
|
+
paths: invalidationPaths,
|
|
762
|
+
wait: invalidation.wait,
|
|
862
763
|
});
|
|
863
|
-
const buildId = hash.digest("hex");
|
|
864
|
-
Logger.debug(`Generated build ID ${buildId}`);
|
|
865
|
-
distribution.createInvalidation(buildId, invalidationPaths);
|
|
866
764
|
}
|
|
867
765
|
}
|
|
868
766
|
static buildDefaultServerCachePolicyProps(allowedHeaders) {
|
|
@@ -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,
|
|
5
|
+
import { BaseSiteFileOptions, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
|
|
6
6
|
import { SSTConstruct } from "./Construct.js";
|
|
7
7
|
import { FunctionBindingProps } from "./util/functionBinding.js";
|
|
8
8
|
export interface StaticSiteProps {
|
|
@@ -67,33 +67,6 @@ export interface StaticSiteProps {
|
|
|
67
67
|
* ```
|
|
68
68
|
*/
|
|
69
69
|
buildOutput?: string;
|
|
70
|
-
/**
|
|
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
|
-
* @default No cache control for HTML files, and a 1 year cache control for JS/CSS files.
|
|
73
|
-
* ```js
|
|
74
|
-
* [
|
|
75
|
-
* {
|
|
76
|
-
* filters: [{ exclude: "*" }, { include: "*.html" }],
|
|
77
|
-
* cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
|
|
78
|
-
* },
|
|
79
|
-
* {
|
|
80
|
-
* filters: [{ exclude: "*" }, { include: "*.js" }, { include: "*.css" }],
|
|
81
|
-
* cacheControl: "max-age=31536000,public,immutable",
|
|
82
|
-
* },
|
|
83
|
-
* ]
|
|
84
|
-
* ```
|
|
85
|
-
* @example
|
|
86
|
-
* ```js
|
|
87
|
-
* new StaticSite(stack, "Site", {
|
|
88
|
-
* buildOutput: "dist",
|
|
89
|
-
* fileOptions: [{
|
|
90
|
-
* filters: [{ exclude: "*" }, { include: "*.js" }],
|
|
91
|
-
* cacheControl: "max-age=31536000,public,immutable",
|
|
92
|
-
* }]
|
|
93
|
-
* });
|
|
94
|
-
* ```
|
|
95
|
-
*/
|
|
96
|
-
fileOptions?: StaticSiteFileOptions[] | StaticSiteFileOptionsDeprecated[];
|
|
97
70
|
/**
|
|
98
71
|
* Pass in a list of placeholder values to be replaced in the website content. For example, the follow configuration:
|
|
99
72
|
*
|
|
@@ -169,6 +142,50 @@ export interface StaticSiteProps {
|
|
|
169
142
|
* ```
|
|
170
143
|
*/
|
|
171
144
|
purgeFiles?: boolean;
|
|
145
|
+
assets?: {
|
|
146
|
+
/**
|
|
147
|
+
* Character encoding for text based assets uploaded to S3 (ex: html, css, js, etc.). If "none" is specified, no charset will be returned in header.
|
|
148
|
+
* @default utf-8
|
|
149
|
+
* @example
|
|
150
|
+
* ```js
|
|
151
|
+
* assets: {
|
|
152
|
+
* textEncoding: "iso-8859-1"
|
|
153
|
+
* }
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
textEncoding?: "utf-8" | "iso-8859-1" | "windows-1252" | "ascii" | "none";
|
|
157
|
+
/**
|
|
158
|
+
* 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.
|
|
159
|
+
* @default No cache control for HTML files, and a 1 year cache control for JS/CSS files.
|
|
160
|
+
* ```js
|
|
161
|
+
* assets: {
|
|
162
|
+
* fileOptions: [
|
|
163
|
+
* {
|
|
164
|
+
* files: "**",
|
|
165
|
+
* cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
|
|
166
|
+
* },
|
|
167
|
+
* {
|
|
168
|
+
* files: "**\/*.{js,css}",
|
|
169
|
+
* cacheControl: "max-age=31536000,public,immutable",
|
|
170
|
+
* },
|
|
171
|
+
* ],
|
|
172
|
+
* }
|
|
173
|
+
* ```
|
|
174
|
+
* @example
|
|
175
|
+
* ```js
|
|
176
|
+
* assets: {
|
|
177
|
+
* fileOptions: [
|
|
178
|
+
* {
|
|
179
|
+
* files: "**\/*.zip",
|
|
180
|
+
* cacheControl: "private,no-cache,no-store,must-revalidate",
|
|
181
|
+
* contentType: "application/zip",
|
|
182
|
+
* },
|
|
183
|
+
* ],
|
|
184
|
+
* }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
fileOptions?: StaticSiteFileOptions[];
|
|
188
|
+
};
|
|
172
189
|
dev?: {
|
|
173
190
|
/**
|
|
174
191
|
* When running `sst dev, site is not deployed. This is to ensure `sst dev` can start up quickly.
|
|
@@ -263,12 +280,7 @@ export interface StaticSiteProps {
|
|
|
263
280
|
}
|
|
264
281
|
export interface StaticSiteDomainProps extends DistributionDomainProps {
|
|
265
282
|
}
|
|
266
|
-
export interface StaticSiteFileOptionsFilter extends BaseSiteFileOptionsFilter {
|
|
267
|
-
}
|
|
268
283
|
export interface StaticSiteFileOptions extends BaseSiteFileOptions {
|
|
269
|
-
filters: StaticSiteFileOptionsFilter[];
|
|
270
|
-
}
|
|
271
|
-
export interface StaticSiteFileOptionsDeprecated extends BaseSiteFileOptionsDeprecated {
|
|
272
284
|
}
|
|
273
285
|
export interface StaticSiteReplaceProps extends BaseSiteReplaceProps {
|
|
274
286
|
}
|
|
@@ -327,6 +339,7 @@ export declare class StaticSite extends Construct implements SSTConstruct {
|
|
|
327
339
|
};
|
|
328
340
|
/** @internal */
|
|
329
341
|
getFunctionBinding(): FunctionBindingProps;
|
|
342
|
+
private validateDeprecatedFileOptions;
|
|
330
343
|
private generateViteTypes;
|
|
331
344
|
private buildApp;
|
|
332
345
|
private createS3Assets;
|