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.
@@ -1,7 +1,7 @@
1
1
  import path from "path";
2
2
  import url from "url";
3
3
  import fs from "fs";
4
- import glob from "glob";
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, cache, nodejs, permissions, environment, bind, customDomain, waitForInvalidation, fileOptions, warm, cdk, } = props;
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 Error(`No site found at "${path.resolve(sitePath)}"`);
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 Error(edge
123
- ? `Timeout must be less than or equal to 30 seconds when the "edge" flag is enabled.`
124
- : `Timeout must be less than or equal to 180 seconds.`);
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 assetFileOptions = fileOptions || createS3OriginAssetFileOptions(props.copy, cache);
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 createS3OriginAssetFileOptions(copy, cache) {
566
- const fileOptions = [];
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) => {
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
- bucket.grantReadWrite(handler);
709
- uploader.grantInvoke(handler);
710
- // Create custom resource
711
- return new CustomResource(self, "S3Deployment", {
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
- Sources: assets.map((asset) => ({
716
- BucketName: asset.s3BucketName,
717
- ObjectKey: asset.s3ObjectKey,
597
+ sources: s3Assets.map((s3Asset) => ({
598
+ bucketName: s3Asset.s3BucketName,
599
+ objectKey: s3Asset.s3ObjectKey,
718
600
  })),
719
- DestinationBucketName: bucket.bucketName,
720
- FileOptions: (fileOptions || []).map((o) => {
721
- const { filters, cacheControl, contentType, contentEncoding } = o;
722
- const { include, exclude } = o;
723
- return [
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 useAwsCliLayer() {
772
- singletonAwsCliLayer =
773
- singletonAwsCliLayer ?? new AwsCliLayer(self, "AwsCliLayer");
774
- return singletonAwsCliLayer;
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 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
- }
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 (cdnInvalidationStrategy === "versioned") {
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
- invalidationPaths.push("/*");
722
+ return;
830
723
  // 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
- }
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, BaseSiteFileOptionsFilter, BaseSiteReplaceProps, BaseSiteCdkDistributionProps, BaseSiteFileOptionsDeprecated } from "./BaseSite.js";
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;