sst 2.31.0 → 2.32.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.
@@ -1,6 +1,8 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import zlib from "zlib";
3
4
  import crypto from "crypto";
5
+ import { globSync } from "glob";
4
6
  import { Duration as CdkDuration, RemovalPolicy, CustomResource, } from "aws-cdk-lib/core";
5
7
  import { Code, Runtime, Function as CdkFunction, Architecture, LayerVersion, } from "aws-cdk-lib/aws-lambda";
6
8
  import { AttributeType, Billing, TableV2 as Table, } from "aws-cdk-lib/aws-dynamodb";
@@ -13,6 +15,9 @@ import { toCdkSize } from "./util/size.js";
13
15
  import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam";
14
16
  import { RetentionDays } from "aws-cdk-lib/aws-logs";
15
17
  import { VisibleError } from "../error.js";
18
+ import { Asset } from "aws-cdk-lib/aws-s3-assets";
19
+ import { useFunctions } from "./Function.js";
20
+ import { useDeferredTasks } from "./deferred_task.js";
16
21
  const LAYER_VERSION = "2";
17
22
  const DEFAULT_OPEN_NEXT_VERSION = "2.2.4";
18
23
  const DEFAULT_CACHE_POLICY_ALLOWED_HEADERS = [
@@ -34,7 +39,11 @@ const DEFAULT_CACHE_POLICY_ALLOWED_HEADERS = [
34
39
  * ```
35
40
  */
36
41
  export class NextjsSite extends SsrSite {
37
- routes;
42
+ _routes;
43
+ routesManifest;
44
+ appPathRoutesManifest;
45
+ appPathsManifest;
46
+ pagesManifest;
38
47
  constructor(scope, id, props) {
39
48
  const streaming = props?.experimental?.streaming ?? false;
40
49
  const disableDynamoDBCache = props?.experimental?.disableDynamoDBCache ?? false;
@@ -57,6 +66,7 @@ export class NextjsSite extends SsrSite {
57
66
  });
58
67
  if (this.isPerRouteLoggingEnabled()) {
59
68
  this.disableDefaultLogging();
69
+ this.uploadSourcemaps();
60
70
  }
61
71
  if (!disableIncrementalCache) {
62
72
  this.createRevalidationQueue();
@@ -80,6 +90,7 @@ export class NextjsSite extends SsrSite {
80
90
  CACHE_BUCKET_REGION: Stack.of(this).region,
81
91
  },
82
92
  });
93
+ this.removeSourcemaps();
83
94
  return this.validatePlan({
84
95
  cloudFrontFunctions: {
85
96
  serverCfFunction: {
@@ -312,7 +323,10 @@ export class NextjsSite extends SsrSite {
312
323
  const injections = [];
313
324
  if (this.isPerRouteLoggingEnabled()) {
314
325
  injections.push(`
315
- const routeData = ${JSON.stringify(this.useRoutes())}.find(({ regex }) => event.rawPath.match(new RegExp(regex)));
326
+ const routeData = ${JSON.stringify(this.useRoutes().map(({ regex, logGroupPath }) => ({
327
+ regex,
328
+ logGroupPath,
329
+ })))}.find(({ regex }) => event.rawPath.match(new RegExp(regex)));
316
330
  if (routeData) {
317
331
  console.log("::sst::" + JSON.stringify({
318
332
  action:"log.split",
@@ -349,53 +363,178 @@ export class NextjsSite extends SsrSite {
349
363
  handler: `${wrapperName}.handler`,
350
364
  };
351
365
  }
366
+ removeSourcemaps() {
367
+ const { path: sitePath } = this.props;
368
+ const files = globSync("**/*.js.map", {
369
+ cwd: path.join(sitePath, ".open-next", "server-function"),
370
+ nodir: true,
371
+ dot: true,
372
+ });
373
+ for (const file of files) {
374
+ fs.rmSync(path.join(sitePath, ".open-next", "server-function", file));
375
+ }
376
+ }
352
377
  useRoutes() {
353
- if (this.routes)
354
- return this.routes;
355
- const id = this.node.id;
378
+ if (this._routes)
379
+ return this._routes;
380
+ const routesManifest = this.useRoutesManifest();
381
+ this._routes = [
382
+ ...[...routesManifest.dynamicRoutes, ...routesManifest.staticRoutes]
383
+ .map(({ page, regex }) => {
384
+ const cwRoute = NextjsSite.buildCloudWatchRouteName(page);
385
+ const cwHash = NextjsSite.buildCloudWatchRouteHash(page);
386
+ const sourcemapPath = this.getSourcemapForAppRoute(page) ||
387
+ this.getSourcemapForPagesRoute(page);
388
+ return {
389
+ route: page,
390
+ regex,
391
+ logGroupPath: `/${cwHash}${cwRoute}`,
392
+ sourcemapPath: sourcemapPath,
393
+ sourcemapKey: cwHash,
394
+ };
395
+ })
396
+ .sort((a, b) => a.route.localeCompare(b.route)),
397
+ ...(routesManifest.dataRoutes || [])
398
+ .map(({ page, dataRouteRegex }) => {
399
+ const routeDisplayName = page.endsWith("/")
400
+ ? `/_next/data/BUILD_ID${page}index.json`
401
+ : `/_next/data/BUILD_ID${page}.json`;
402
+ const cwRoute = NextjsSite.buildCloudWatchRouteName(routeDisplayName);
403
+ const cwHash = NextjsSite.buildCloudWatchRouteHash(page);
404
+ return {
405
+ route: routeDisplayName,
406
+ regex: dataRouteRegex,
407
+ logGroupPath: `/${cwHash}${cwRoute}`,
408
+ };
409
+ })
410
+ .sort((a, b) => a.route.localeCompare(b.route)),
411
+ ];
412
+ return this._routes;
413
+ }
414
+ useRoutesManifest() {
415
+ if (this.routesManifest)
416
+ return this.routesManifest;
356
417
  const { path: sitePath } = this.props;
418
+ const id = this.node.id;
357
419
  try {
358
- const content = JSON.parse(fs
420
+ const content = fs
359
421
  .readFileSync(path.join(sitePath, ".next/routes-manifest.json"))
360
- .toString());
361
- this.routes = [
362
- ...[...content.dynamicRoutes, ...content.staticRoutes]
363
- .map(({ page, regex }) => {
364
- const cwRoute = NextjsSite.buildCloudWatchRouteName(page);
365
- const cwHash = NextjsSite.buildCloudWatchRouteHash(page);
366
- return {
367
- route: page,
368
- regex,
369
- logGroupPath: `/${cwHash}${cwRoute}`,
370
- };
371
- })
372
- .sort((a, b) => a.route.localeCompare(b.route)),
373
- ...(content.dataRoutes || [])
374
- .map(({ page, dataRouteRegex }) => {
375
- const routeDisplayName = page.endsWith("/")
376
- ? `/_next/data/BUILD_ID${page}index.json`
377
- : `/_next/data/BUILD_ID${page}.json`;
378
- const cwRoute = NextjsSite.buildCloudWatchRouteName(routeDisplayName);
379
- const cwHash = NextjsSite.buildCloudWatchRouteHash(`data:${page}`);
380
- return {
381
- route: routeDisplayName,
382
- regex: dataRouteRegex,
383
- logGroupPath: `/${cwHash}${cwRoute}`,
384
- };
385
- })
386
- .sort((a, b) => a.route.localeCompare(b.route)),
387
- ];
388
- return this.routes;
422
+ .toString();
423
+ this.routesManifest = JSON.parse(content);
424
+ return this.routesManifest;
389
425
  }
390
426
  catch (e) {
391
427
  console.error(e);
392
428
  throw new VisibleError(`Failed to read routes data from ".next/routes-manifest.json" for the "${id}" site.`);
393
429
  }
394
430
  }
431
+ useAppPathRoutesManifest() {
432
+ if (this.appPathRoutesManifest)
433
+ return this.appPathRoutesManifest;
434
+ const { path: sitePath } = this.props;
435
+ try {
436
+ const content = fs
437
+ .readFileSync(path.join(sitePath, ".next/app-path-routes-manifest.json"))
438
+ .toString();
439
+ this.appPathRoutesManifest = JSON.parse(content);
440
+ return this.appPathRoutesManifest;
441
+ }
442
+ catch (e) {
443
+ return {};
444
+ }
445
+ }
446
+ useAppPathsManifest() {
447
+ if (this.appPathsManifest)
448
+ return this.appPathsManifest;
449
+ const { path: sitePath } = this.props;
450
+ try {
451
+ const content = fs
452
+ .readFileSync(path.join(sitePath, ".next/server/app-paths-manifest.json"))
453
+ .toString();
454
+ this.appPathsManifest = JSON.parse(content);
455
+ return this.appPathsManifest;
456
+ }
457
+ catch (e) {
458
+ return {};
459
+ }
460
+ }
461
+ usePagesManifest() {
462
+ if (this.pagesManifest)
463
+ return this.pagesManifest;
464
+ const { path: sitePath } = this.props;
465
+ try {
466
+ const content = fs
467
+ .readFileSync(path.join(sitePath, ".next/server/pages-manifest.json"))
468
+ .toString();
469
+ this.pagesManifest = JSON.parse(content);
470
+ return this.pagesManifest;
471
+ }
472
+ catch (e) {
473
+ return {};
474
+ }
475
+ }
395
476
  getBuildId() {
396
477
  const { path: sitePath } = this.props;
397
478
  return fs.readFileSync(path.join(sitePath, ".next/BUILD_ID")).toString();
398
479
  }
480
+ getSourcemapForAppRoute(page) {
481
+ const { path: sitePath } = this.props;
482
+ // Step 1: look up in "appPathRoutesManifest" to find the key with
483
+ // value equal to the page
484
+ // {
485
+ // "/_not-found": "/_not-found",
486
+ // "/about/page": "/about",
487
+ // "/about/profile/page": "/about/profile",
488
+ // "/page": "/",
489
+ // "/favicon.ico/route": "/favicon.ico"
490
+ // }
491
+ const appPathRoutesManifest = this.useAppPathRoutesManifest();
492
+ const appPathRoute = Object.keys(appPathRoutesManifest).find((key) => appPathRoutesManifest[key] === page);
493
+ if (!appPathRoute)
494
+ return;
495
+ // Step 2: look up in "appPathsManifest" to find the file with key equal
496
+ // to the page
497
+ // {
498
+ // "/_not-found": "app/_not-found.js",
499
+ // "/about/page": "app/about/page.js",
500
+ // "/about/profile/page": "app/about/profile/page.js",
501
+ // "/page": "app/page.js",
502
+ // "/favicon.ico/route": "app/favicon.ico/route.js"
503
+ // }
504
+ const appPathsManifest = this.useAppPathsManifest();
505
+ const filePath = appPathsManifest[appPathRoute];
506
+ if (!filePath)
507
+ return;
508
+ // Step 3: check the .map file exists
509
+ const sourcemapPath = path.join(sitePath, ".next", "server", `${filePath}.map`);
510
+ if (!fs.existsSync(sourcemapPath))
511
+ return;
512
+ return sourcemapPath;
513
+ }
514
+ getSourcemapForPagesRoute(page) {
515
+ const { path: sitePath } = this.props;
516
+ // Step 1: look up in "pathsManifest" to find the file with key equal
517
+ // to the page
518
+ // {
519
+ // "/_app": "pages/_app.js",
520
+ // "/_error": "pages/_error.js",
521
+ // "/404": "pages/404.html",
522
+ // "/api/hello": "pages/api/hello.js",
523
+ // "/api/auth/[...nextauth]": "pages/api/auth/[...nextauth].js",
524
+ // "/api/next-auth-restricted": "pages/api/next-auth-restricted.js",
525
+ // "/": "pages/index.js",
526
+ // "/ssr": "pages/ssr.js"
527
+ // }
528
+ const pagesManifest = this.usePagesManifest();
529
+ const filePath = pagesManifest[page];
530
+ if (!filePath)
531
+ return;
532
+ // Step 2: check the .map file exists
533
+ const sourcemapPath = path.join(sitePath, ".next", "server", `${filePath}.map`);
534
+ if (!fs.existsSync(sourcemapPath))
535
+ return;
536
+ return sourcemapPath;
537
+ }
399
538
  isPerRouteLoggingEnabled() {
400
539
  return (!this.doNotDeploy &&
401
540
  !this.props.edge &&
@@ -424,6 +563,28 @@ export class NextjsSite extends SsrSite {
424
563
  });
425
564
  server.role?.attachInlinePolicy(policy);
426
565
  }
566
+ uploadSourcemaps() {
567
+ const stack = Stack.of(this);
568
+ const server = this.serverFunction;
569
+ this.useRoutes().forEach(({ sourcemapPath, sourcemapKey }) => {
570
+ if (!sourcemapPath || !sourcemapKey)
571
+ return;
572
+ useDeferredTasks().add(async () => {
573
+ // zip sourcemap
574
+ const zipPath = `${sourcemapPath}.gz.zip`;
575
+ const data = await fs.promises.readFile(sourcemapPath);
576
+ await fs.promises.writeFile(zipPath, zlib.gzipSync(data));
577
+ const asset = new Asset(this, `Sourcemap-${sourcemapKey}`, {
578
+ path: zipPath,
579
+ });
580
+ useFunctions().sourcemaps.add(stack.stackName, {
581
+ srcBucket: asset.bucket,
582
+ srcKey: asset.s3ObjectKey,
583
+ tarKey: path.join(server.functionArn, sourcemapKey),
584
+ });
585
+ });
586
+ });
587
+ }
427
588
  static buildCloudWatchRouteName(route) {
428
589
  return route.replace(/[^a-zA-Z0-9_\-/.#]/g, "");
429
590
  }
@@ -375,7 +375,6 @@ type ServiceNormalizedProps = ServiceProps & {
375
375
  memory: Exclude<ServiceProps["memory"], undefined>;
376
376
  port: Exclude<ServiceProps["port"], undefined>;
377
377
  logRetention: Exclude<ServiceProps["logRetention"], undefined>;
378
- waitForInvalidation: Exclude<ServiceProps["waitForInvalidation"], undefined>;
379
378
  };
380
379
  /**
381
380
  * The `Service` construct is a higher level CDK construct that makes it easy to create modern web apps with Server Side Rendering capabilities.
@@ -159,7 +159,6 @@ export class Service extends Construct {
159
159
  memory: props?.memory || "0.5 GB",
160
160
  port: props?.port || 3000,
161
161
  logRetention: props?.logRetention || "infinite",
162
- waitForInvalidation: false,
163
162
  ...props,
164
163
  };
165
164
  this.doNotDeploy =
@@ -645,7 +644,7 @@ export class Service extends Construct {
645
644
  platform: architecture === "arm64" ? Platform.LINUX_ARM64 : Platform.LINUX_AMD64,
646
645
  file: dockerfile,
647
646
  buildArgs: build?.buildArgs,
648
- exclude: [".sst"],
647
+ exclude: [".sst/dist", ".sst/artifacts"],
649
648
  ignoreMode: IgnoreMode.GLOB,
650
649
  });
651
650
  const cfnTask = taskDefinition.node.defaultChild;
@@ -1,7 +1,7 @@
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, CachePolicyProps, ErrorResponse } from "aws-cdk-lib/aws-cloudfront";
4
+ import { ICachePolicy, IResponseHeadersPolicy, ViewerProtocolPolicy, 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";
@@ -214,21 +214,6 @@ export interface SsrSiteProps {
214
214
  * ```
215
215
  */
216
216
  textEncoding?: "utf-8" | "iso-8859-1" | "windows-1252" | "ascii" | "none";
217
- /**
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.
219
- * - "never" - No invalidation will be performed.
220
- * - "all" - All files will be invalidated when any file changes. (Default, requires checking file content which will increase deployment time)
221
- * - "versioned" - Only versioned files will be invalidated when versioned files change.
222
- * - "always" - All files are invalidated on every deployment.
223
- * @default all
224
- * @example
225
- * ```js
226
- * assets: {
227
- * cdnInvalidationStrategy: "versioned"
228
- * }
229
- * ```
230
- */
231
- cdnInvalidationStrategy?: "never" | "all" | "versioned" | "always";
232
217
  /**
233
218
  * The TTL for versioned files (ex: `main-1234.css`) in the CDN and browser cache. Ignored when `versionedFilesCacheHeader` is specified.
234
219
  * @default 1 year
@@ -294,9 +279,45 @@ export interface SsrSiteProps {
294
279
  */
295
280
  _uploadConcurrency?: number;
296
281
  };
282
+ invalidation?: {
283
+ /**
284
+ * 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.
285
+ * @default false
286
+ * @example
287
+ * ```js
288
+ * invalidation: {
289
+ * wait: true,
290
+ * }
291
+ * ```
292
+ */
293
+ wait?: boolean;
294
+ /**
295
+ * The paths to invalidate. There are three built-in options:
296
+ * - "none" - No invalidation will be performed.
297
+ * - "all" - All files will be invalidated when any file changes.
298
+ * - "versioned" - Only versioned files will be invalidated when versioned files change.
299
+ * Alternatively you can pass in an array of paths to invalidate.
300
+ * @default "all"
301
+ * @example
302
+ * Disable invalidation:
303
+ * ```js
304
+ * invalidation: {
305
+ * paths: "none",
306
+ * }
307
+ * ```
308
+ * Invalidate "index.html" and all files under the "products" route:
309
+ * ```js
310
+ * invalidation: {
311
+ * paths: ["/index.html", "/products/*"],
312
+ * }
313
+ * ```
314
+ */
315
+ paths?: "none" | "all" | "versioned" | string[];
316
+ };
297
317
  /**
298
318
  * 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.
299
319
  * @default false
320
+ * @deprecated Use `invalidation.wait` instead.
300
321
  */
301
322
  waitForInvalidation?: boolean;
302
323
  cdk?: {
@@ -340,16 +361,32 @@ export interface SsrSiteProps {
340
361
  * from the server rendering Lambda.
341
362
  */
342
363
  responseHeadersPolicy?: IResponseHeadersPolicy;
364
+ /**
365
+ * Override the CloudFront viewer protocol policy properties.
366
+ * @default ViewerProtocolPolicy.REDIRECT_TO_HTTPS
367
+ * @example
368
+ * ```js
369
+ * import { ViewerProtocolPolicy } from "aws-cdk-lib/aws-cloudfront";
370
+ *
371
+ * cdk: {
372
+ * viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
373
+ * }
374
+ * ```
375
+ */
376
+ viewerProtocolPolicy?: ViewerProtocolPolicy;
343
377
  server?: Pick<CdkFunctionProps, "layers" | "vpc" | "vpcSubnets" | "securityGroups" | "allowAllOutbound" | "allowPublicSubnet" | "architecture" | "logRetention"> & Pick<FunctionProps, "copyFiles">;
344
378
  };
345
379
  }
380
+ type SsrSiteInvalidationNormalizedProps = Exclude<SsrSiteProps["invalidation"], undefined>;
346
381
  export type SsrSiteNormalizedProps = SsrSiteProps & {
347
382
  path: Exclude<SsrSiteProps["path"], undefined>;
348
383
  typesPath: Exclude<SsrSiteProps["typesPath"], undefined>;
349
384
  runtime: Exclude<SsrSiteProps["runtime"], undefined>;
350
385
  timeout: Exclude<SsrSiteProps["timeout"], undefined>;
351
386
  memorySize: Exclude<SsrSiteProps["memorySize"], undefined>;
352
- waitForInvalidation: Exclude<SsrSiteProps["waitForInvalidation"], undefined>;
387
+ invalidation: Exclude<SsrSiteProps["invalidation"], undefined> & {
388
+ paths: Exclude<SsrSiteInvalidationNormalizedProps["paths"], undefined>;
389
+ };
353
390
  };
354
391
  /**
355
392
  * The `SsrSite` construct is a higher level CDK construct that makes it easy to create modern web apps with Server Side Rendering capabilities.
@@ -55,18 +55,22 @@ export class SsrSite extends Construct {
55
55
  const props = {
56
56
  path: ".",
57
57
  typesPath: ".",
58
- waitForInvalidation: false,
59
58
  runtime: "nodejs18.x",
60
59
  timeout: "10 seconds",
61
60
  memorySize: "1024 MB",
62
61
  ...rawProps,
62
+ invalidation: {
63
+ wait: rawProps?.waitForInvalidation,
64
+ paths: "all",
65
+ ...rawProps?.invalidation,
66
+ },
63
67
  };
64
68
  this.id = id;
65
69
  this.props = props;
66
70
  const app = scope.node.root;
67
71
  const stack = Stack.of(this);
68
72
  const self = this;
69
- const { path: sitePath, typesPath, buildCommand, runtime, timeout, memorySize, edge, regional, dev, assets, nodejs, permissions, environment, bind, customDomain, waitForInvalidation, 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;
70
74
  this.doNotDeploy = !stack.isActive || (app.mode === "dev" && !dev?.deploy);
71
75
  validateSiteExists();
72
76
  validateTimeout();
@@ -259,7 +263,6 @@ export class SsrSite extends Construct {
259
263
  const distribution = new Distribution(self, "CDN", {
260
264
  scopeOverride: self,
261
265
  customDomain,
262
- waitForInvalidation,
263
266
  cdk: {
264
267
  distribution: {
265
268
  // these values can be overwritten
@@ -305,7 +308,7 @@ export class SsrSite extends Construct {
305
308
  if (behavior.cacheType === "static") {
306
309
  return {
307
310
  origin,
308
- viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
311
+ viewerProtocolPolicy: cdk?.viewerProtocolPolicy ?? ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
309
312
  allowedMethods: behavior.allowedMethods ?? AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
310
313
  cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
311
314
  compress: true,
@@ -323,7 +326,7 @@ export class SsrSite extends Construct {
323
326
  }
324
327
  else if (behavior.cacheType === "server") {
325
328
  return {
326
- viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
329
+ viewerProtocolPolicy: cdk?.viewerProtocolPolicy ?? ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
327
330
  origin,
328
331
  allowedMethods: behavior.allowedMethods ?? AllowedMethods.ALLOW_ALL,
329
332
  cachedMethods: CachedMethods.CACHE_GET_HEAD_OPTIONS,
@@ -689,18 +692,7 @@ function handler(event) {
689
692
  return replaceValues;
690
693
  }
691
694
  function createDistributionInvalidation() {
692
- const cdnInvalidationStrategy = assets?.cdnInvalidationStrategy ?? "all";
693
- if (cdnInvalidationStrategy === "never")
694
- return;
695
- if (plan.buildId) {
696
- distribution.createInvalidation(plan.buildId);
697
- return;
698
- }
699
- if (cdnInvalidationStrategy === "always") {
700
- const buildId = Date.now().toString(16) + Math.random().toString(16).slice(2);
701
- distribution.createInvalidation(buildId);
702
- return;
703
- }
695
+ const paths = invalidation.paths;
704
696
  // We will generate a hash based on the contents of the S3 files with cache enabled.
705
697
  // This will be used to determine if we need to invalidate our CloudFront cache.
706
698
  const s3Origin = Object.values(plan.origins).find((origin) => origin.type === "s3");
@@ -711,46 +703,64 @@ function handler(event) {
711
703
  return;
712
704
  // Build invalidation paths
713
705
  const invalidationPaths = [];
714
- if (cdnInvalidationStrategy === "versioned") {
706
+ if (paths === "none") {
707
+ }
708
+ else if (paths === "all") {
709
+ invalidationPaths.push("/*");
710
+ }
711
+ else if (paths === "versioned") {
715
712
  cachedS3Files.forEach((item) => {
716
713
  if (!item.versionedSubDir)
717
714
  return;
718
715
  invalidationPaths.push(path.posix.join("/", item.to, item.versionedSubDir, "*"));
719
716
  });
720
717
  }
718
+ else {
719
+ invalidationPaths.push(...paths);
720
+ }
721
721
  if (invalidationPaths.length === 0)
722
- invalidationPaths.push("/*");
722
+ return;
723
723
  // Build build ID
724
- const hash = crypto.createHash("md5");
725
- cachedS3Files.forEach((item) => {
726
- // The below options are needed to support following symlinks when building zip files:
727
- // - nodir: This will prevent symlinks themselves from being copied into the zip.
728
- // - follow: This will follow symlinks and copy the files within.
729
- // For versioned files, use file path for digest since file version in name should change on content change
730
- if (item.versionedSubDir) {
731
- globSync("**", {
732
- dot: true,
733
- nodir: true,
734
- follow: true,
735
- cwd: path.resolve(sitePath, item.from, item.versionedSubDir),
736
- }).forEach((filePath) => hash.update(filePath));
737
- }
738
- // For non-versioned files, use file content for digest
739
- if (cdnInvalidationStrategy === "all") {
740
- globSync("**", {
741
- ignore: item.versionedSubDir
742
- ? [path.posix.join(item.versionedSubDir, "**")]
743
- : undefined,
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))));
749
- }
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,
750
763
  });
751
- const buildId = hash.digest("hex");
752
- Logger.debug(`Generated build ID ${buildId}`);
753
- distribution.createInvalidation(buildId, invalidationPaths);
754
764
  }
755
765
  }
756
766
  static buildDefaultServerCachePolicyProps(allowedHeaders) {
@@ -49,7 +49,6 @@ export class StaticSite extends Construct {
49
49
  this.id = id;
50
50
  this.props = {
51
51
  path: ".",
52
- waitForInvalidation: false,
53
52
  ...props,
54
53
  };
55
54
  this.doNotDeploy =
@@ -74,7 +73,9 @@ export class StaticSite extends Construct {
74
73
  const s3deployCR = this.createS3Deployment(assets, filenamesAsset);
75
74
  this.distribution.node.addDependency(s3deployCR);
76
75
  // Invalidate CloudFront
77
- this.distribution.createInvalidation(this.generateInvalidationId(assets));
76
+ this.distribution.createInvalidation({
77
+ version: this.generateInvalidationId(assets),
78
+ });
78
79
  });
79
80
  app.registerTypes(this);
80
81
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sideEffects": false,
3
3
  "name": "sst",
4
- "version": "2.31.0",
4
+ "version": "2.32.1",
5
5
  "bin": {
6
6
  "sst": "cli/sst.js"
7
7
  },
@@ -120,7 +120,7 @@
120
120
  "@types/ws": "^8.5.3",
121
121
  "@types/yargs": "^17.0.13",
122
122
  "archiver": "^5.3.1",
123
- "astro-sst": "2.31.0",
123
+ "astro-sst": "2.32.1",
124
124
  "async": "^3.2.4",
125
125
  "tsx": "^3.12.1",
126
126
  "typescript": "^5.2.2",