sst 2.30.4 → 2.31.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,20 +1,9 @@
1
1
  import { ErrorResponse, DistributionProps, BehaviorOptions, IOrigin } from "aws-cdk-lib/aws-cloudfront";
2
- export interface BaseSiteFileOptionsFilter {
3
- include?: string;
4
- exclude?: string;
5
- }
6
2
  export interface BaseSiteFileOptions {
7
- filters: BaseSiteFileOptionsFilter[];
8
- cacheControl?: string;
9
- contentType?: string;
10
- contentEncoding?: string;
11
- }
12
- export interface BaseSiteFileOptionsDeprecated {
13
- include?: string | string[];
14
- exclude?: string | string[];
3
+ files: string | string[];
4
+ ignore?: string | string[];
15
5
  cacheControl?: string;
16
6
  contentType?: string;
17
- contentEncoding?: string;
18
7
  }
19
8
  export interface BaseSiteEnvironmentOutputsInfo {
20
9
  path: string;
@@ -27,7 +27,7 @@ export interface NextjsSiteProps extends Omit<SsrSiteProps, "nodejs"> {
27
27
  * logging: "per-route",
28
28
  * ```
29
29
  */
30
- _logging?: "combined" | "per-route";
30
+ logging?: "combined" | "per-route";
31
31
  imageOptimization?: {
32
32
  /**
33
33
  * The amount of memory in MB allocated for image optimization function.
@@ -13,7 +13,7 @@ import { toCdkSize } from "./util/size.js";
13
13
  import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam";
14
14
  import { RetentionDays } from "aws-cdk-lib/aws-logs";
15
15
  import { VisibleError } from "../error.js";
16
- const LAYER_VERSION = "9";
16
+ const LAYER_VERSION = "2";
17
17
  const DEFAULT_OPEN_NEXT_VERSION = "2.2.4";
18
18
  const DEFAULT_CACHE_POLICY_ALLOWED_HEADERS = [
19
19
  "accept",
@@ -305,7 +305,7 @@ export class NextjsSite extends SsrSite {
305
305
  };
306
306
  }
307
307
  wrapServerFunction(config) {
308
- const { path: sitePath, experimental } = this.props;
308
+ const { path: sitePath, experimental, cdk } = this.props;
309
309
  const stack = Stack.of(this);
310
310
  const wrapperName = "nextjssite-index";
311
311
  const serverPath = path.join(sitePath, ".open-next", "server-function");
@@ -341,7 +341,9 @@ export class NextjsSite extends SsrSite {
341
341
  ...config,
342
342
  layers: this.isPerRouteLoggingEnabled()
343
343
  ? [
344
- LayerVersion.fromLayerVersionArn(this, "SSTExtension", `arn:aws:lambda:${stack.region}:226609089145:layer:sst-extension:${LAYER_VERSION}`),
344
+ LayerVersion.fromLayerVersionArn(this, "SSTExtension", cdk?.server?.architecture?.name === Architecture.X86_64.name
345
+ ? `arn:aws:lambda:${stack.region}:226609089145:layer:sst-extension-amd64:${LAYER_VERSION}`
346
+ : `arn:aws:lambda:${stack.region}:226609089145:layer:sst-extension-arm64:${LAYER_VERSION}`),
345
347
  ]
346
348
  : undefined,
347
349
  handler: `${wrapperName}.handler`,
@@ -397,9 +399,11 @@ export class NextjsSite extends SsrSite {
397
399
  isPerRouteLoggingEnabled() {
398
400
  return (!this.doNotDeploy &&
399
401
  !this.props.edge &&
400
- this.props._logging === "per-route");
402
+ this.props.logging === "per-route");
401
403
  }
402
404
  disableDefaultLogging() {
405
+ // Note: keep default logs enabled
406
+ return;
403
407
  const stack = Stack.of(this);
404
408
  const server = this.serverFunction;
405
409
  const policy = new Policy(this, "DisableLoggingPolicy", {
package/constructs/RDS.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import path from "path";
2
- import glob from "glob";
2
+ import { globSync } from "glob";
3
3
  import fs from "fs";
4
4
  import url from "url";
5
5
  import * as crypto from "crypto";
@@ -347,7 +347,7 @@ export class RDS extends Construct {
347
347
  }
348
348
  generateMigrationsHash(migrations) {
349
349
  // Get all files inside the migrations folder
350
- const files = glob.sync("**", {
350
+ const files = globSync("**", {
351
351
  dot: true,
352
352
  nodir: true,
353
353
  follow: true,
@@ -8,7 +8,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, BaseSiteFileOptionsFilter, BaseSiteFileOptionsDeprecated, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
11
+ import { BaseSiteFileOptions, 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";
@@ -52,12 +52,7 @@ export interface SsrSiteNodeJSProps extends NodeJSProps {
52
52
  }
53
53
  export interface SsrDomainProps extends DistributionDomainProps {
54
54
  }
55
- export interface SsrSiteFileOptionsFilter extends BaseSiteFileOptionsFilter {
56
- }
57
55
  export interface SsrSiteFileOptions extends BaseSiteFileOptions {
58
- filters: SsrSiteFileOptionsFilter[];
59
- }
60
- export interface SsrSiteFileOptionsDeprecated extends BaseSiteFileOptionsDeprecated {
61
56
  }
62
57
  export interface SsrSiteReplaceProps extends BaseSiteReplaceProps {
63
58
  }
@@ -207,18 +202,18 @@ export interface SsrSiteProps {
207
202
  */
208
203
  url?: string;
209
204
  };
210
- cache?: {
205
+ assets?: {
211
206
  /**
212
- * 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.
207
+ * 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.
213
208
  * @default utf-8
214
209
  * @example
215
210
  * ```js
216
- * cache: {
217
- * textEncoding: "iso-8859-1"
211
+ * assets: {
212
+ * textEncoding: "iso-8859-1"
218
213
  * }
219
214
  * ```
220
215
  */
221
- textEncoding?: "UTF-8" | "ISO-8859-1" | "Windows-1252" | "ASCII" | "none";
216
+ textEncoding?: "utf-8" | "iso-8859-1" | "windows-1252" | "ascii" | "none";
222
217
  /**
223
218
  * 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.
224
219
  * - "never" - No invalidation will be performed.
@@ -228,7 +223,7 @@ export interface SsrSiteProps {
228
223
  * @default all
229
224
  * @example
230
225
  * ```js
231
- * cache: {
226
+ * assets: {
232
227
  * cdnInvalidationStrategy: "versioned"
233
228
  * }
234
229
  * ```
@@ -239,8 +234,8 @@ export interface SsrSiteProps {
239
234
  * @default 1 year
240
235
  * @example
241
236
  * ```js
242
- * cache: {
243
- * versionedFilesTTL: "30 days"
237
+ * assets: {
238
+ * versionedFilesTTL: "30 days"
244
239
  * }
245
240
  * ```
246
241
  */
@@ -250,7 +245,7 @@ export interface SsrSiteProps {
250
245
  * @default public,max-age=31536000,immutable
251
246
  * @example
252
247
  * ```js
253
- * cache: {
248
+ * assets: {
254
249
  * versionedFilesCacheHeader: "public,max-age=31536000,immutable"
255
250
  * }
256
251
  * ```
@@ -261,8 +256,8 @@ export interface SsrSiteProps {
261
256
  * @default 1 day
262
257
  * @example
263
258
  * ```js
264
- * cache: {
265
- * nonVersionedFilesTTL: "4 hours"
259
+ * assets: {
260
+ * nonVersionedFilesTTL: "4 hours"
266
261
  * }
267
262
  * ```
268
263
  */
@@ -272,7 +267,7 @@ export interface SsrSiteProps {
272
267
  * @default public,max-age=0,s-maxage=86400,stale-while-revalidate=8640
273
268
  * @example
274
269
  * ```js
275
- * cache: {
270
+ * assets: {
276
271
  * nonVersionedFilesCacheHeader: "public,max-age=0,no-cache"
277
272
  * }
278
273
  * ```
@@ -282,10 +277,10 @@ export interface SsrSiteProps {
282
277
  * 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.
283
278
  * @example
284
279
  * ```js
285
- * cache: {
280
+ * assets: {
286
281
  * fileOptions: [
287
282
  * {
288
- * filters: [ { exclude: "*" }, { include: "*.zip" } ],
283
+ * files: "**\/*.zip",
289
284
  * cacheControl: "private,no-cache,no-store,must-revalidate",
290
285
  * contentType: "application/zip",
291
286
  * },
@@ -294,6 +289,10 @@ export interface SsrSiteProps {
294
289
  * ```
295
290
  */
296
291
  fileOptions?: SsrSiteFileOptions[];
292
+ /**
293
+ * @internal
294
+ */
295
+ _uploadConcurrency?: number;
297
296
  };
298
297
  /**
299
298
  * 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.
@@ -343,59 +342,6 @@ export interface SsrSiteProps {
343
342
  responseHeadersPolicy?: IResponseHeadersPolicy;
344
343
  server?: Pick<CdkFunctionProps, "layers" | "vpc" | "vpcSubnets" | "securityGroups" | "allowAllOutbound" | "allowPublicSubnet" | "architecture" | "logRetention"> & Pick<FunctionProps, "copyFiles">;
345
344
  };
346
- /**
347
- * 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.
348
- * @deprecated Use `cache.fileOptions` instead. Note that the `cache.fileOptions` are appended to default file options, not a replacement as this prop was.
349
- * @default
350
- * Versioned files cached for 1 year at the CDN and browser level.
351
- * Nonversioned files cached for 1 day at the CDN level, but not at the browser level.
352
- * ```js
353
- * fileOptions: [
354
- * {
355
- * exclude: "*",
356
- * include: "{versioned_directory}/*",
357
- * cacheControl: "public,max-age=31536000,immutable",
358
- * },
359
- * {
360
- * exclude: "*",
361
- * include: "[{non_versioned_file1}, {non_versioned_file2}, ...]",
362
- * cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
363
- * },
364
- * {
365
- * exclude: "*",
366
- * include: "[{non_versioned_dir_1}/*, {non_versioned_dir_2}/*, ...]",
367
- * cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
368
- * },
369
- * ]
370
- * ```
371
- *
372
- * @example
373
- * ```js
374
- * fileOptions: [
375
- * {
376
- * exclude: "*",
377
- * include: "{versioned_directory}/*.css",
378
- * cacheControl: "public,max-age=31536000,immutable",
379
- * contentType: "text/css; charset=UTF-8",
380
- * },
381
- * {
382
- * exclude: "*",
383
- * include: "{versioned_directory}/*.js",
384
- * cacheControl: "public,max-age=31536000,immutable",
385
- * },
386
- * {
387
- * exclude: "*",
388
- * include: "[{non_versioned_file1}, {non_versioned_file2}, ...]",
389
- * cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
390
- * },
391
- * {
392
- * exclude: "*",
393
- * include: "[{non_versioned_dir_1}/*, {non_versioned_dir_2}/*, ...]",
394
- * cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
395
- * },
396
- * ]
397
- */
398
- fileOptions?: SsrSiteFileOptionsDeprecated[];
399
345
  }
400
346
  export type SsrSiteNormalizedProps = SsrSiteProps & {
401
347
  path: Exclude<SsrSiteProps["path"], undefined>;
@@ -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";
@@ -67,10 +66,11 @@ export class SsrSite extends Construct {
67
66
  const app = scope.node.root;
68
67
  const stack = Stack.of(this);
69
68
  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;
69
+ const { path: sitePath, typesPath, buildCommand, runtime, timeout, memorySize, edge, regional, dev, assets, nodejs, permissions, environment, bind, customDomain, waitForInvalidation, warm, cdk, } = props;
71
70
  this.doNotDeploy = !stack.isActive || (app.mode === "dev" && !dev?.deploy);
72
71
  validateSiteExists();
73
72
  validateTimeout();
73
+ validateDeprecatedFileOptions();
74
74
  writeTypesFile(typesPath);
75
75
  useSites().add(stack.stackName, id, this.constructor.name, props);
76
76
  if (this.doNotDeploy) {
@@ -82,7 +82,6 @@ export class SsrSite extends Construct {
82
82
  }
83
83
  let s3DeployCRs = [];
84
84
  let ssrFunctions = [];
85
- let singletonAwsCliLayer;
86
85
  let singletonUrlSigner;
87
86
  let singletonCachePolicy;
88
87
  let singletonOriginRequestPolicy;
@@ -110,7 +109,7 @@ export class SsrSite extends Construct {
110
109
  app.registerTypes(this);
111
110
  function validateSiteExists() {
112
111
  if (!fs.existsSync(sitePath)) {
113
- throw new Error(`No site found at "${path.resolve(sitePath)}"`);
112
+ throw new VisibleError(`No site found at "${path.resolve(sitePath)}"`);
114
113
  }
115
114
  }
116
115
  function validateTimeout() {
@@ -119,9 +118,15 @@ export class SsrSite extends Construct {
119
118
  : toCdkDuration(timeout).toSeconds();
120
119
  const limit = edge ? 30 : 180;
121
120
  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.`);
121
+ throw new VisibleError(edge
122
+ ? `In the "${id}" construct, timeout must be less than or equal to 30 seconds when the "edge" flag is enabled.`
123
+ : `In the "${id}" construct, timeout must be less than or equal to 180 seconds.`);
124
+ }
125
+ }
126
+ function validateDeprecatedFileOptions() {
127
+ // @ts-expect-error
128
+ if (props.fileOptions) {
129
+ 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
130
  }
126
131
  }
127
132
  function writeTypesFile(typesPath) {
@@ -411,8 +416,7 @@ function handler(event) {
411
416
  originPath: "/" + (props.originPath ?? ""),
412
417
  });
413
418
  const assets = createS3OriginAssets(props.copy);
414
- const assetFileOptions = fileOptions || createS3OriginAssetFileOptions(props.copy, cache);
415
- const s3deployCR = createS3OriginDeployment(assets, assetFileOptions);
419
+ const s3deployCR = createS3OriginDeployment(props.copy, assets);
416
420
  s3DeployCRs.push(s3deployCR);
417
421
  return s3Origin;
418
422
  }
@@ -562,185 +566,44 @@ function handler(event) {
562
566
  }
563
567
  return assets;
564
568
  }
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
- },
569
+ function createS3OriginDeployment(copy, s3Assets) {
570
+ const policy = new Policy(self, "S3UploaderPolicy", {
571
+ statements: [
572
+ new PolicyStatement({
573
+ effect: Effect.ALLOW,
574
+ actions: ["lambda:InvokeFunction"],
575
+ resources: [stack.customResourceHandler.functionArn],
576
+ }),
577
+ new PolicyStatement({
578
+ effect: Effect.ALLOW,
579
+ actions: ["s3:ListBucket", "s3:PutObject", "s3:DeleteObject"],
580
+ resources: [bucket.bucketArn, `${bucket.bucketArn}/*`],
581
+ }),
582
+ new PolicyStatement({
583
+ effect: Effect.ALLOW,
584
+ actions: ["s3:GetObject"],
585
+ resources: [`${s3Assets[0].bucket.bucketArn}/*`],
586
+ }),
587
+ ],
707
588
  });
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",
589
+ stack.customResourceHandler.role?.attachInlinePolicy(policy);
590
+ const resource = new CustomResource(self, "S3Uploader", {
591
+ serviceToken: stack.customResourceHandler.functionArn,
592
+ resourceType: "Custom::S3Uploader",
714
593
  properties: {
715
- Sources: assets.map((asset) => ({
716
- BucketName: asset.s3BucketName,
717
- ObjectKey: asset.s3ObjectKey,
594
+ sources: s3Assets.map((s3Asset) => ({
595
+ bucketName: s3Asset.s3BucketName,
596
+ objectKey: s3Asset.s3ObjectKey,
718
597
  })),
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(),
598
+ destinationBucketName: bucket.bucketName,
599
+ concurrency: assets?._uploadConcurrency,
600
+ textEncoding: assets?.textEncoding ?? "utf-8",
601
+ fileOptions: getS3FileOptions(copy),
602
+ replaceValues: getS3ContentReplaceValues(),
742
603
  },
743
604
  });
605
+ resource.node.addDependency(policy);
606
+ return resource;
744
607
  }
745
608
  function useFunctionUrlSigningFunction() {
746
609
  singletonUrlSigner =
@@ -768,10 +631,40 @@ function handler(event) {
768
631
  OriginRequestPolicy.fromOriginRequestPolicyId(self, "ServerOriginRequestPolicy", "b689b0a8-53d0-40ab-baf2-68738e2966ac");
769
632
  return singletonOriginRequestPolicy;
770
633
  }
771
- function useAwsCliLayer() {
772
- singletonAwsCliLayer =
773
- singletonAwsCliLayer ?? new AwsCliLayer(self, "AwsCliLayer");
774
- return singletonAwsCliLayer;
634
+ function getS3FileOptions(copy) {
635
+ const fileOptions = [];
636
+ const nonVersionedFilesTTL = typeof assets?.nonVersionedFilesTTL === "number"
637
+ ? assets.nonVersionedFilesTTL
638
+ : toCdkDuration(assets?.nonVersionedFilesTTL ?? "1 day").toSeconds();
639
+ const staleWhileRevalidateTTL = Math.max(Math.floor(nonVersionedFilesTTL / 10), 30);
640
+ const versionedFilesTTL = typeof assets?.versionedFilesTTL === "number"
641
+ ? assets.versionedFilesTTL
642
+ : toCdkDuration(assets?.versionedFilesTTL ?? "365 days").toSeconds();
643
+ copy.forEach(({ cached, to, versionedSubDir }) => {
644
+ if (!cached)
645
+ return;
646
+ // Create a default file option for: unversioned files
647
+ fileOptions.push({
648
+ files: "**",
649
+ ignore: versionedSubDir
650
+ ? path.posix.join(to, versionedSubDir, "**")
651
+ : undefined,
652
+ cacheControl: assets?.nonVersionedFilesCacheHeader ??
653
+ `public,max-age=0,s-maxage=${nonVersionedFilesTTL},stale-while-revalidate=${staleWhileRevalidateTTL}`,
654
+ });
655
+ // Create a default file option for: versioned files
656
+ if (versionedSubDir) {
657
+ fileOptions.push({
658
+ files: path.posix.join(to, versionedSubDir, "**"),
659
+ cacheControl: assets?.versionedFilesCacheHeader ??
660
+ `public,max-age=${versionedFilesTTL},immutable`,
661
+ });
662
+ }
663
+ });
664
+ if (assets?.fileOptions) {
665
+ fileOptions.push(...assets.fileOptions);
666
+ }
667
+ return fileOptions;
775
668
  }
776
669
  function getS3ContentReplaceValues() {
777
670
  const replaceValues = [];
@@ -796,7 +689,7 @@ function handler(event) {
796
689
  return replaceValues;
797
690
  }
798
691
  function createDistributionInvalidation() {
799
- const cdnInvalidationStrategy = cache?.cdnInvalidationStrategy ?? "all";
692
+ const cdnInvalidationStrategy = assets?.cdnInvalidationStrategy ?? "all";
800
693
  if (cdnInvalidationStrategy === "never")
801
694
  return;
802
695
  if (plan.buildId) {
@@ -833,31 +726,26 @@ function handler(event) {
833
726
  // The below options are needed to support following symlinks when building zip files:
834
727
  // - nodir: This will prevent symlinks themselves from being copied into the zip.
835
728
  // - 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
729
  // For versioned files, use file path for digest since file version in name should change on content change
843
730
  if (item.versionedSubDir) {
844
- glob
845
- .sync("**", {
846
- ...globOptions,
731
+ globSync("**", {
732
+ dot: true,
733
+ nodir: true,
734
+ follow: true,
847
735
  cwd: path.resolve(sitePath, item.from, item.versionedSubDir),
848
- })
849
- .forEach((filePath) => hash.update(filePath));
736
+ }).forEach((filePath) => hash.update(filePath));
850
737
  }
851
738
  // For non-versioned files, use file content for digest
852
739
  if (cdnInvalidationStrategy === "all") {
853
- glob
854
- .sync("**", {
855
- ...globOptions,
740
+ globSync("**", {
856
741
  ignore: item.versionedSubDir
857
742
  ? [path.posix.join(item.versionedSubDir, "**")]
858
743
  : undefined,
859
- })
860
- .forEach((filePath) => hash.update(fs.readFileSync(path.resolve(sitePath, item.from, filePath))));
744
+ dot: true,
745
+ nodir: true,
746
+ follow: true,
747
+ cwd: path.resolve(sitePath, item.from),
748
+ }).forEach((filePath) => hash.update(fs.readFileSync(path.resolve(sitePath, item.from, filePath))));
861
749
  }
862
750
  });
863
751
  const buildId = hash.digest("hex");