sst 2.22.11 → 2.23.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.
@@ -7,26 +7,20 @@ import { Construct } from "constructs";
7
7
  import { Token, Duration, RemovalPolicy, CustomResource, } from "aws-cdk-lib/core";
8
8
  import { BlockPublicAccess, Bucket, } from "aws-cdk-lib/aws-s3";
9
9
  import { Asset } from "aws-cdk-lib/aws-s3-assets";
10
- import { Effect, Policy, PolicyStatement } from "aws-cdk-lib/aws-iam";
11
10
  import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda";
12
- import { HostedZone, ARecord, AaaaRecord, RecordTarget, } from "aws-cdk-lib/aws-route53";
13
- import { CloudFrontTarget } from "aws-cdk-lib/aws-route53-targets";
14
- import { Distribution, Function as CfFunction, FunctionCode as CfFunctionCode, FunctionEventType as CfFunctionEventType, ViewerProtocolPolicy, } from "aws-cdk-lib/aws-cloudfront";
11
+ import { Function as CfFunction, FunctionCode as CfFunctionCode, FunctionEventType as CfFunctionEventType, ViewerProtocolPolicy, } from "aws-cdk-lib/aws-cloudfront";
15
12
  import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins";
16
13
  import { AwsCliLayer } from "aws-cdk-lib/lambda-layer-awscli";
17
14
  import { Stack } from "./Stack.js";
15
+ import { Distribution } from "./Distribution.js";
18
16
  import { getBuildCmdEnvironment, buildErrorResponsesFor404ErrorPage, buildErrorResponsesForRedirectToIndex, } from "./BaseSite.js";
19
17
  import { useDeferredTasks } from "./deferred_task.js";
20
- import { HttpsRedirect } from "./cdk/website-redirect.js";
21
- import { DnsValidatedCertificate } from "./cdk/dns-validated-certificate.js";
22
18
  import { isCDKConstruct } from "./Construct.js";
23
19
  import { getParameterPath, } from "./util/functionBinding.js";
24
20
  import { gray } from "colorette";
25
21
  import { useProject } from "../project.js";
22
+ import { createAppContext } from "./context.js";
26
23
  const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
27
- /////////////////////
28
- // Construct
29
- /////////////////////
30
24
  /**
31
25
  * The `StaticSite` construct is a higher level CDK construct that makes it easy to create a static website.
32
26
  *
@@ -48,8 +42,6 @@ export class StaticSite extends Construct {
48
42
  doNotDeploy;
49
43
  bucket;
50
44
  distribution;
51
- hostedZone;
52
- certificate;
53
45
  constructor(scope, id, props) {
54
46
  super(scope, props?.cdk?.id || id);
55
47
  const app = scope.node.root;
@@ -62,22 +54,15 @@ export class StaticSite extends Construct {
62
54
  };
63
55
  this.doNotDeploy =
64
56
  !stack.isActive || (app.mode === "dev" && !this.props.dev?.deploy);
65
- this.validateCustomDomainSettings();
66
57
  this.generateViteTypes();
58
+ useSites().add(stack.stackName, id, this.props);
67
59
  if (this.doNotDeploy) {
68
60
  // @ts-ignore
69
61
  this.bucket = this.distribution = null;
70
62
  return;
71
63
  }
72
- // Create Bucket
73
64
  this.bucket = this.createS3Bucket();
74
- // Create Custom Domain
75
- this.hostedZone = this.lookupHostedZone();
76
- this.certificate = this.createCertificate();
77
- // Create CloudFront
78
65
  this.distribution = this.createCfDistribution();
79
- // Connect Custom Domain to CloudFront Distribution
80
- this.createRoute53Records();
81
66
  useDeferredTasks().add(async () => {
82
67
  // Build app
83
68
  this.buildApp();
@@ -88,7 +73,7 @@ export class StaticSite extends Construct {
88
73
  const s3deployCR = this.createS3Deployment(cliLayer, assets, filenamesAsset);
89
74
  this.distribution.node.addDependency(s3deployCR);
90
75
  // Invalidate CloudFront
91
- this.createCloudFrontInvalidation(assets);
76
+ this.distribution.createInvalidation(this.generateInvalidationId(assets));
92
77
  });
93
78
  }
94
79
  /**
@@ -97,7 +82,7 @@ export class StaticSite extends Construct {
97
82
  get url() {
98
83
  if (this.doNotDeploy)
99
84
  return this.props.dev?.url;
100
- return `https://${this.distribution.distributionDomainName}`;
85
+ return this.distribution.url;
101
86
  }
102
87
  /**
103
88
  * If the custom domain is enabled, this is the URL of the website with the custom domain.
@@ -123,9 +108,9 @@ export class StaticSite extends Construct {
123
108
  return;
124
109
  return {
125
110
  bucket: this.bucket,
126
- distribution: this.distribution,
127
- hostedZone: this.hostedZone,
128
- certificate: this.certificate,
111
+ distribution: this.distribution.cdk.distribution,
112
+ hostedZone: this.distribution.cdk.hostedZone,
113
+ certificate: this.distribution.cdk.certificate,
129
114
  };
130
115
  }
131
116
  getConstructMetadata() {
@@ -383,92 +368,34 @@ interface ImportMeta {
383
368
  // CloudFront Distribution
384
369
  /////////////////////
385
370
  createCfDistribution() {
386
- const { cdk, errorPage } = this.props;
387
- const isImportedCloudFrontDistribution = (distribution) => {
388
- return distribution !== undefined && isCDKConstruct(distribution);
389
- };
390
- // cdk.distribution is an imported construct
391
- if (isImportedCloudFrontDistribution(cdk?.distribution)) {
392
- return cdk?.distribution;
393
- }
394
- // Validate input
395
- if (cdk?.distribution?.certificate) {
396
- throw new Error(`Do not configure the "cfDistribution.certificate". Use the "customDomain" to configure the domain certificate.`);
397
- }
398
- if (cdk?.distribution?.domainNames) {
399
- throw new Error(`Do not configure the "cfDistribution.domainNames". Use the "customDomain" to configure the domain name.`);
400
- }
401
- if (errorPage && cdk?.distribution?.errorResponses) {
402
- throw new Error(`Cannot configure the "cfDistribution.errorResponses" when "errorPage" is passed in. Use one or the other to configure the behavior for error pages.`);
403
- }
404
- // Create CloudFront distribution
371
+ const { errorPage, customDomain, cdk } = this.props;
405
372
  const indexPage = this.props.indexPage || "index.html";
406
- return new Distribution(this, "Distribution", {
407
- // these values can be overwritten by cfDistributionProps
408
- defaultRootObject: indexPage,
409
- errorResponses: !errorPage || errorPage === "redirect_to_index_page"
410
- ? buildErrorResponsesForRedirectToIndex(indexPage)
411
- : buildErrorResponsesFor404ErrorPage(errorPage),
412
- ...cdk?.distribution,
413
- // these values can NOT be overwritten by cfDistributionProps
414
- domainNames: this.buildDistributionDomainNames(),
415
- certificate: this.certificate,
416
- defaultBehavior: this.buildDistributionBehavior(),
373
+ return new Distribution(this, "CDN", {
374
+ scopeOverride: this,
375
+ customDomain,
376
+ cdk: {
377
+ distribution: cdk?.distribution && isCDKConstruct(cdk.distribution)
378
+ ? cdk.distribution
379
+ : {
380
+ // these values can be overwritten by cfDistributionProps
381
+ defaultRootObject: indexPage,
382
+ errorResponses: !errorPage || errorPage === "redirect_to_index_page"
383
+ ? buildErrorResponsesForRedirectToIndex(indexPage)
384
+ : buildErrorResponsesFor404ErrorPage(errorPage),
385
+ ...cdk?.distribution,
386
+ // these values can NOT be overwritten by cfDistributionProps
387
+ defaultBehavior: this.buildDistributionBehavior(),
388
+ },
389
+ },
417
390
  });
418
391
  }
419
- createCloudFrontInvalidation(assets) {
392
+ generateInvalidationId(assets) {
420
393
  const stack = Stack.of(this);
421
394
  // Need the AssetHash field so the CR gets updated on each deploy
422
- const assetsHash = crypto
395
+ return crypto
423
396
  .createHash("md5")
424
397
  .update(assets.map(({ assetHash }) => assetHash).join(""))
425
398
  .digest("hex");
426
- const policy = new Policy(this, "CloudFrontInvalidatorPolicy", {
427
- statements: [
428
- new PolicyStatement({
429
- effect: Effect.ALLOW,
430
- actions: [
431
- "cloudfront:GetInvalidation",
432
- "cloudfront:CreateInvalidation",
433
- ],
434
- resources: [
435
- `arn:${stack.partition}:cloudfront::${stack.account}:distribution/${this.distribution.distributionId}`,
436
- ],
437
- }),
438
- ],
439
- });
440
- stack.customResourceHandler.role?.attachInlinePolicy(policy);
441
- const resource = new CustomResource(this, "CloudFrontInvalidator", {
442
- serviceToken: stack.customResourceHandler.functionArn,
443
- resourceType: "Custom::CloudFrontInvalidator",
444
- properties: {
445
- assetsHash,
446
- distributionId: this.distribution.distributionId,
447
- paths: ["/*"],
448
- waitForInvalidation: this.props.waitForInvalidation,
449
- },
450
- });
451
- resource.node.addDependency(policy);
452
- return resource;
453
- }
454
- buildDistributionDomainNames() {
455
- const { customDomain } = this.props;
456
- const domainNames = [];
457
- if (!customDomain) {
458
- // no domain
459
- }
460
- else if (typeof customDomain === "string") {
461
- domainNames.push(customDomain);
462
- }
463
- else {
464
- domainNames.push(customDomain.domainName);
465
- if (customDomain.alternateNames) {
466
- if (!customDomain.cdk?.certificate)
467
- throw new Error("Certificates for alternate domains cannot be automatically created. Please specify certificate to use");
468
- domainNames.push(...customDomain.alternateNames);
469
- }
470
- }
471
- return domainNames;
472
399
  }
473
400
  buildDistributionBehavior() {
474
401
  const { cdk } = this.props;
@@ -507,128 +434,6 @@ function handler(event) {
507
434
  };
508
435
  }
509
436
  /////////////////////
510
- // Custom Domain
511
- /////////////////////
512
- validateCustomDomainSettings() {
513
- const { customDomain } = this.props;
514
- if (!customDomain) {
515
- return;
516
- }
517
- if (typeof customDomain === "string") {
518
- return;
519
- }
520
- if (customDomain.isExternalDomain === true) {
521
- if (!customDomain.cdk?.certificate) {
522
- throw new Error(`A valid certificate is required when "isExternalDomain" is set to "true".`);
523
- }
524
- if (customDomain.domainAlias) {
525
- throw new Error(`Domain alias is only supported for domains hosted on Amazon Route 53. Do not set the "customDomain.domainAlias" when "isExternalDomain" is enabled.`);
526
- }
527
- if (customDomain.hostedZone) {
528
- throw new Error(`Hosted zones can only be configured for domains hosted on Amazon Route 53. Do not set the "customDomain.hostedZone" when "isExternalDomain" is enabled.`);
529
- }
530
- }
531
- }
532
- lookupHostedZone() {
533
- const { customDomain } = this.props;
534
- // Skip if customDomain is not configured
535
- if (!customDomain) {
536
- return;
537
- }
538
- let hostedZone;
539
- if (typeof customDomain === "string") {
540
- hostedZone = HostedZone.fromLookup(this, "HostedZone", {
541
- domainName: customDomain,
542
- });
543
- }
544
- else if (customDomain.cdk?.hostedZone) {
545
- hostedZone = customDomain.cdk.hostedZone;
546
- }
547
- else if (typeof customDomain.hostedZone === "string") {
548
- hostedZone = HostedZone.fromLookup(this, "HostedZone", {
549
- domainName: customDomain.hostedZone,
550
- });
551
- }
552
- else if (typeof customDomain.domainName === "string") {
553
- // Skip if domain is not a Route53 domain
554
- if (customDomain.isExternalDomain === true) {
555
- return;
556
- }
557
- hostedZone = HostedZone.fromLookup(this, "HostedZone", {
558
- domainName: customDomain.domainName,
559
- });
560
- }
561
- else {
562
- hostedZone = customDomain.hostedZone;
563
- }
564
- return hostedZone;
565
- }
566
- createCertificate() {
567
- const { customDomain } = this.props;
568
- if (!customDomain) {
569
- return;
570
- }
571
- let acmCertificate;
572
- // HostedZone is set for Route 53 domains
573
- if (this.hostedZone) {
574
- if (typeof customDomain === "string") {
575
- acmCertificate = new DnsValidatedCertificate(this, "Certificate", {
576
- domainName: customDomain,
577
- hostedZone: this.hostedZone,
578
- region: "us-east-1",
579
- });
580
- }
581
- else if (customDomain.cdk?.certificate) {
582
- acmCertificate = customDomain.cdk.certificate;
583
- }
584
- else {
585
- acmCertificate = new DnsValidatedCertificate(this, "Certificate", {
586
- domainName: customDomain.domainName,
587
- hostedZone: this.hostedZone,
588
- region: "us-east-1",
589
- });
590
- }
591
- }
592
- // HostedZone is NOT set for non-Route 53 domains
593
- else {
594
- if (typeof customDomain !== "string") {
595
- acmCertificate = customDomain.cdk?.certificate;
596
- }
597
- }
598
- return acmCertificate;
599
- }
600
- createRoute53Records() {
601
- const { customDomain } = this.props;
602
- if (!customDomain || !this.hostedZone) {
603
- return;
604
- }
605
- let recordName;
606
- let domainAlias;
607
- if (typeof customDomain === "string") {
608
- recordName = customDomain;
609
- }
610
- else {
611
- recordName = customDomain.domainName;
612
- domainAlias = customDomain.domainAlias;
613
- }
614
- // Create DNS record
615
- const recordProps = {
616
- recordName,
617
- zone: this.hostedZone,
618
- target: RecordTarget.fromAlias(new CloudFrontTarget(this.distribution)),
619
- };
620
- new ARecord(this, "AliasRecord", recordProps);
621
- new AaaaRecord(this, "AliasRecordAAAA", recordProps);
622
- // Create Alias redirect record
623
- if (domainAlias) {
624
- new HttpsRedirect(this, "Redirect", {
625
- zone: this.hostedZone,
626
- recordNames: [domainAlias],
627
- targetDomain: recordName,
628
- });
629
- }
630
- }
631
- /////////////////////
632
437
  // Helper Functions
633
438
  /////////////////////
634
439
  getS3ContentReplaceValues() {
@@ -650,3 +455,14 @@ function handler(event) {
650
455
  return replaceValues;
651
456
  }
652
457
  }
458
+ export const useSites = createAppContext(() => {
459
+ const sites = [];
460
+ return {
461
+ add(stack, name, props) {
462
+ sites.push({ stack, name, props });
463
+ },
464
+ get all() {
465
+ return sites;
466
+ },
467
+ };
468
+ });
@@ -5,10 +5,11 @@ import * as route53 from "aws-cdk-lib/aws-route53";
5
5
  import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
6
6
  import * as acm from "aws-cdk-lib/aws-certificatemanager";
7
7
  import { SSTConstruct } from "../Construct.js";
8
- import { BaseSiteDomainProps, BaseSiteCdkDistributionProps } from "../BaseSite.js";
8
+ import { DistributionDomainProps } from "../Distribution.js";
9
+ import { BaseSiteCdkDistributionProps } from "../BaseSite.js";
9
10
  import { Permissions } from "../util/permission.js";
10
11
  import { FunctionBindingProps } from "../util/functionBinding.js";
11
- export interface NextjsDomainProps extends BaseSiteDomainProps {
12
+ export interface NextjsDomainProps extends DistributionDomainProps {
12
13
  }
13
14
  export interface NextjsCdkDistributionProps extends BaseSiteCdkDistributionProps {
14
15
  }
@@ -298,3 +299,11 @@ export declare class NextjsSite extends Construct implements SSTConstruct {
298
299
  private getLambdaContentReplaceValues;
299
300
  private normalizeRuntime;
300
301
  }
302
+ export declare const useSites: () => {
303
+ add(stack: string, name: string, props: NextjsSiteProps): void;
304
+ readonly all: {
305
+ stack: string;
306
+ name: string;
307
+ props: NextjsSiteProps;
308
+ }[];
309
+ };
@@ -28,6 +28,7 @@ import { getParameterPath, } from "../util/functionBinding.js";
28
28
  import * as crossRegionHelper from "./cross-region-helper.js";
29
29
  import { gray, red } from "colorette";
30
30
  import { useProject } from "../../project.js";
31
+ import { createAppContext } from "../context.js";
31
32
  const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
32
33
  /////////////////////
33
34
  // Construct
@@ -129,6 +130,7 @@ export class NextjsSite extends Construct {
129
130
  this.props = props;
130
131
  this.cdk = {};
131
132
  this.awsCliLayer = new AwsCliLayer(this, "AwsCliLayer");
133
+ useSites().add(stack.stackName, id, this.props);
132
134
  // Build app
133
135
  if (this.isPlaceholder) {
134
136
  this.buildOutDir = null;
@@ -1060,3 +1062,14 @@ export class NextjsSite extends Construct {
1060
1062
  return lambda.Runtime.NODEJS_16_X;
1061
1063
  }
1062
1064
  }
1065
+ export const useSites = createAppContext(() => {
1066
+ const sites = [];
1067
+ return {
1068
+ add(stack, name, props) {
1069
+ sites.push({ stack, name, props });
1070
+ },
1071
+ get all() {
1072
+ return sites;
1073
+ },
1074
+ };
1075
+ });
@@ -12,6 +12,7 @@ export * from "./Table.js";
12
12
  export * from "./Topic.js";
13
13
  export * from "./Bucket.js";
14
14
  export * from "./Script.js";
15
+ export * from "./Service.js";
15
16
  export * from "./EventBus.js";
16
17
  export * from "./Function.js";
17
18
  export * from "./AppSyncApi.js";
@@ -12,6 +12,7 @@ export * from "./Table.js";
12
12
  export * from "./Topic.js";
13
13
  export * from "./Bucket.js";
14
14
  export * from "./Script.js";
15
+ export * from "./Service.js";
15
16
  export * from "./EventBus.js";
16
17
  export * from "./Function.js";
17
18
  export * from "./AppSyncApi.js";
@@ -31,10 +31,13 @@ type EventPayload<E extends Event> = {
31
31
  type: E["type"];
32
32
  properties: E["shape"]["properties"];
33
33
  metadata: undefined extends E["shape"]["metadata"] ? E["shape"]["metadataFn"] : E["shape"]["metadata"];
34
+ attempts: number;
34
35
  };
35
36
  export declare function EventHandler<Events extends Event>(_events: Events | Events[], cb: (evt: {
36
37
  [K in Events["type"]]: EventPayload<Extract<Events, {
37
38
  type: K;
38
39
  }>>;
39
- }[Events["type"]]) => Promise<void>): (event: EventBridgeEvent<string, any>) => Promise<void>;
40
+ }[Events["type"]]) => Promise<void>): (event: EventBridgeEvent<string, any> & {
41
+ attempts?: number;
42
+ }) => Promise<void>;
40
43
  export {};
@@ -51,6 +51,7 @@ export function EventHandler(_events, cb) {
51
51
  type: event["detail-type"],
52
52
  properties: event.detail.properties,
53
53
  metadata: event.detail.metadata,
54
+ attempts: event.attempts ?? 0,
54
55
  });
55
56
  };
56
57
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sideEffects": false,
3
3
  "name": "sst",
4
- "version": "2.22.11",
4
+ "version": "2.23.0",
5
5
  "bin": {
6
6
  "sst": "cli/sst.js"
7
7
  },
@@ -32,6 +32,7 @@
32
32
  "@aws-cdk/cloudformation-diff": "2.84.0",
33
33
  "@aws-cdk/cx-api": "2.84.0",
34
34
  "@aws-sdk/client-cloudformation": "^3.279.0",
35
+ "@aws-sdk/client-ecs": "^3.279.0",
35
36
  "@aws-sdk/client-eventbridge": "^3.342.0",
36
37
  "@aws-sdk/client-iam": "^3.279.0",
37
38
  "@aws-sdk/client-iot": "^3.279.0",
package/project.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface ConfigOptions {
16
16
  disableParameterizedStackNameCheck?: boolean;
17
17
  };
18
18
  bootstrap?: {
19
+ useExistingBucket?: string;
19
20
  stackName?: string;
20
21
  tags?: Record<string, string>;
21
22
  };