sst 2.13.9 → 2.14.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.
@@ -31,6 +31,7 @@ export declare class EdgeFunction extends Construct {
31
31
  constructor(scope: Construct, id: string, props: EdgeFunctionProps);
32
32
  get currentVersion(): IVersion;
33
33
  attachPermissions(permissions: Permissions): void;
34
+ addEnvironment(key: string, value: string): void;
34
35
  private buildAssetFromHandler;
35
36
  private buildAssetFromBundle;
36
37
  private bind;
@@ -89,6 +89,17 @@ export class EdgeFunction extends Construct {
89
89
  attachPermissions(permissions) {
90
90
  attachPermissionsToRole(this.role, permissions);
91
91
  }
92
+ addEnvironment(key, value) {
93
+ // Note: addEnvironment currently only updates AssetReplacer's
94
+ // "_SST_FUNCTION_ENVIRONMENT_" replacements
95
+ this.props.environment[key] = value;
96
+ const cfnReplacer = this.assetReplacer.node
97
+ .defaultChild;
98
+ cfnReplacer.addPropertyOverride("replacements.0.replace", JSON.stringify({
99
+ ...this.props.environment,
100
+ ...this.bindingEnvs,
101
+ }));
102
+ }
92
103
  buildAssetFromHandler(onBundled) {
93
104
  const { nodejs } = this.props;
94
105
  useFunctions().add(this.node.addr, {
@@ -42,11 +42,15 @@ export declare class NextjsSite extends SsrSite {
42
42
  waitForInvalidation: Exclude<NextjsSiteProps["waitForInvalidation"], undefined>;
43
43
  };
44
44
  constructor(scope: Construct, id: string, props?: NextjsSiteProps);
45
+ protected createRevalidation(): void;
45
46
  protected initBuildConfig(): {
46
47
  typesPath: string;
47
48
  serverBuildOutputFile: string;
48
49
  clientBuildOutputDir: string;
49
50
  clientBuildVersionedSubDir: string;
51
+ clientBuildS3KeyPrefix: string;
52
+ prerenderedBuildOutputDir: string;
53
+ prerenderedBuildS3KeyPrefix: string;
50
54
  };
51
55
  protected createFunctionForRegional(): CdkFunction;
52
56
  protected createFunctionForEdge(): EdgeFunction;
@@ -8,6 +8,8 @@ import { Distribution, ViewerProtocolPolicy, AllowedMethods, LambdaEdgeEventType
8
8
  import { S3Origin, HttpOrigin, OriginGroup, } from "aws-cdk-lib/aws-cloudfront-origins";
9
9
  import { Rule, Schedule } from "aws-cdk-lib/aws-events";
10
10
  import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
11
+ import { Queue } from "aws-cdk-lib/aws-sqs";
12
+ import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
11
13
  import { Stack } from "./Stack.js";
12
14
  import { SsrFunction } from "./SsrFunction.js";
13
15
  import { EdgeFunction } from "./EdgeFunction.js";
@@ -28,10 +30,34 @@ import { toCdkDuration } from "./util/duration.js";
28
30
  export class NextjsSite extends SsrSite {
29
31
  constructor(scope, id, props) {
30
32
  super(scope, id, {
31
- buildCommand: "npx --yes open-next@1.4.0 build",
33
+ buildCommand: "npx --yes open-next@2.0.0 build",
32
34
  ...props,
33
35
  });
36
+ if (this.doNotDeploy)
37
+ return;
34
38
  this.createWarmer();
39
+ this.createRevalidation();
40
+ }
41
+ createRevalidation() {
42
+ if (!this.serverLambdaForRegional && !this.serverLambdaForEdge)
43
+ return;
44
+ const queue = new Queue(this, "RevalidationQueue", {
45
+ fifo: true,
46
+ receiveMessageWaitTime: CdkDuration.seconds(20),
47
+ });
48
+ const consumer = new CdkFunction(this, "RevalidationFunction", {
49
+ description: "Next.js revalidator",
50
+ handler: "index.handler",
51
+ code: Code.fromAsset(path.join(this.props.path, ".open-next", "revalidation-function")),
52
+ runtime: Runtime.NODEJS_18_X,
53
+ timeout: CdkDuration.seconds(30),
54
+ });
55
+ consumer.addEventSource(new SqsEventSource(queue, { batchSize: 5 }));
56
+ // Allow server to send messages to the queue
57
+ const server = this.serverLambdaForRegional || this.serverLambdaForEdge;
58
+ server?.addEnvironment("REVALIDATION_QUEUE_URL", queue.queueUrl);
59
+ server?.addEnvironment("REVALIDATION_QUEUE_REGION", Stack.of(this).region);
60
+ queue.grantSendMessages(server?.role);
35
61
  }
36
62
  initBuildConfig() {
37
63
  return {
@@ -39,6 +65,9 @@ export class NextjsSite extends SsrSite {
39
65
  serverBuildOutputFile: ".open-next/server-function/index.mjs",
40
66
  clientBuildOutputDir: ".open-next/assets",
41
67
  clientBuildVersionedSubDir: "_next",
68
+ clientBuildS3KeyPrefix: "_assets",
69
+ prerenderedBuildOutputDir: ".open-next/cache",
70
+ prerenderedBuildS3KeyPrefix: "_cache",
42
71
  };
43
72
  }
44
73
  createFunctionForRegional() {
@@ -52,7 +81,12 @@ export class NextjsSite extends SsrSite {
52
81
  memorySize,
53
82
  bind,
54
83
  permissions,
55
- environment,
84
+ environment: {
85
+ ...environment,
86
+ CACHE_BUCKET_NAME: this.bucket.bucketName,
87
+ CACHE_BUCKET_KEY_PREFIX: "_cache",
88
+ CACHE_BUCKET_REGION: Stack.of(this).region,
89
+ },
56
90
  ...cdk?.server,
57
91
  });
58
92
  return ssrFn.function;
@@ -67,7 +101,12 @@ export class NextjsSite extends SsrSite {
67
101
  memorySize,
68
102
  bind,
69
103
  permissions,
70
- environment,
104
+ environment: {
105
+ ...environment,
106
+ CACHE_BUCKET_NAME: this.bucket.bucketName,
107
+ CACHE_BUCKET_KEY_PREFIX: "_cache",
108
+ CACHE_BUCKET_REGION: Stack.of(this).region,
109
+ },
71
110
  });
72
111
  }
73
112
  createImageOptimizationFunction() {
@@ -90,6 +129,7 @@ export class NextjsSite extends SsrSite {
90
129
  architecture: Architecture.ARM_64,
91
130
  environment: {
92
131
  BUCKET_NAME: this.cdk.bucket.bucketName,
132
+ BUCKET_KEY_PREFIX: "_assets",
93
133
  },
94
134
  initialPolicy: [
95
135
  new PolicyStatement({
@@ -201,7 +241,9 @@ export class NextjsSite extends SsrSite {
201
241
  */
202
242
  const { timeout, cdk } = this.props;
203
243
  const cfDistributionProps = cdk?.distribution || {};
204
- const s3Origin = new S3Origin(this.cdk.bucket);
244
+ const s3Origin = new S3Origin(this.cdk.bucket, {
245
+ originPath: "/" + this.buildConfig.clientBuildS3KeyPrefix,
246
+ });
205
247
  const serverFnUrl = this.serverLambdaForRegional.addFunctionUrl({
206
248
  authType: FunctionUrlAuthType.NONE,
207
249
  });
@@ -240,7 +282,9 @@ export class NextjsSite extends SsrSite {
240
282
  createCloudFrontDistributionForEdge() {
241
283
  const { cdk } = this.props;
242
284
  const cfDistributionProps = cdk?.distribution || {};
243
- const s3Origin = new S3Origin(this.cdk.bucket);
285
+ const s3Origin = new S3Origin(this.cdk.bucket, {
286
+ originPath: "/" + this.buildConfig.clientBuildS3KeyPrefix,
287
+ });
244
288
  const cachePolicy = cdk?.serverCachePolicy ??
245
289
  this.buildServerCachePolicy([
246
290
  "accept",
@@ -249,7 +293,7 @@ export class NextjsSite extends SsrSite {
249
293
  "next-router-state-tree",
250
294
  ]);
251
295
  const originRequestPolicy = this.buildServerOriginRequestPolicy();
252
- const functionVersion = this.serverEdgeFunction.currentVersion;
296
+ const functionVersion = this.serverLambdaForEdge.currentVersion;
253
297
  const serverBehavior = this.buildServerBehaviorForEdge(functionVersion, s3Origin, cachePolicy, originRequestPolicy);
254
298
  return new Distribution(this, "Distribution", {
255
299
  // these values can be overwritten by cfDistributionProps
@@ -1,6 +1,6 @@
1
1
  import { Construct } from "constructs";
2
2
  import { Bucket, BucketProps, IBucket } from "aws-cdk-lib/aws-s3";
3
- import { Function as CdkFunction, FunctionProps } from "aws-cdk-lib/aws-lambda";
3
+ import { Function as CdkFunction, IFunction as ICdkFunction, FunctionProps } from "aws-cdk-lib/aws-lambda";
4
4
  import { IHostedZone } from "aws-cdk-lib/aws-route53";
5
5
  import { Distribution, ICachePolicy, IResponseHeadersPolicy, BehaviorOptions, CachePolicy, Function as CfFunction, FunctionEventType as CfFunctionEventType } from "aws-cdk-lib/aws-cloudfront";
6
6
  import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
@@ -18,7 +18,9 @@ export type SsrBuildConfig = {
18
18
  serverCFFunctionInjection?: string;
19
19
  clientBuildOutputDir: string;
20
20
  clientBuildVersionedSubDir: string;
21
+ clientBuildS3KeyPrefix?: string;
21
22
  prerenderedBuildOutputDir?: string;
23
+ prerenderedBuildS3KeyPrefix?: string;
22
24
  };
23
25
  export interface SsrSiteNodeJSProps extends NodeJSProps {
24
26
  }
@@ -211,13 +213,13 @@ type SsrSiteNormalizedProps = SsrSiteProps & {
211
213
  export declare abstract class SsrSite extends Construct implements SSTConstruct {
212
214
  readonly id: string;
213
215
  protected props: SsrSiteNormalizedProps;
214
- private doNotDeploy;
216
+ protected doNotDeploy: boolean;
215
217
  protected buildConfig: SsrBuildConfig;
216
- protected serverEdgeFunction?: EdgeFunction;
217
- private serverLambdaForEdge?;
218
+ private serverLambdaCdkFunctionForEdge?;
219
+ protected serverLambdaForEdge?: EdgeFunction;
218
220
  protected serverLambdaForRegional?: CdkFunction;
219
221
  private serverLambdaForDev?;
220
- private bucket;
222
+ protected bucket: Bucket;
221
223
  private cfFunction;
222
224
  private distribution;
223
225
  private hostedZone?;
@@ -236,7 +238,7 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
236
238
  * The internally created CDK resources.
237
239
  */
238
240
  get cdk(): {
239
- function: CdkFunction | undefined;
241
+ function: ICdkFunction | undefined;
240
242
  bucket: Bucket;
241
243
  distribution: Distribution;
242
244
  hostedZone: IHostedZone | undefined;
@@ -278,8 +280,8 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
278
280
  protected createFunctionForRegional(): CdkFunction;
279
281
  protected createFunctionForEdge(): EdgeFunction;
280
282
  protected createFunctionForDev(): CdkFunction;
281
- private createFunctionPermissionsForRegional;
282
- private createFunctionPermissionsForEdge;
283
+ private grantServerS3Permissions;
284
+ private grantServerCloudFrontPermissions;
283
285
  private validateCloudFrontDistributionSettings;
284
286
  private createCloudFrontFunction;
285
287
  protected createCloudFrontDistributionForRegional(): Distribution;
@@ -46,7 +46,7 @@ export class SsrSite extends Construct {
46
46
  props;
47
47
  doNotDeploy;
48
48
  buildConfig;
49
- serverEdgeFunction;
49
+ serverLambdaCdkFunctionForEdge;
50
50
  serverLambdaForEdge;
51
51
  serverLambdaForRegional;
52
52
  serverLambdaForDev;
@@ -88,17 +88,16 @@ export class SsrSite extends Construct {
88
88
  this.bucket = this.createS3Bucket();
89
89
  // Create Server functions
90
90
  if (this.props.edge) {
91
- this.serverEdgeFunction = this.createFunctionForEdge();
92
- this.serverLambdaForEdge = CdkFunction.fromFunctionAttributes(this, "IEdgeFunction", {
93
- functionArn: this.serverEdgeFunction.functionArn,
94
- role: this.serverEdgeFunction.role,
91
+ this.serverLambdaForEdge = this.createFunctionForEdge();
92
+ this.serverLambdaCdkFunctionForEdge = CdkFunction.fromFunctionAttributes(this, "IEdgeFunction", {
93
+ functionArn: this.serverLambdaForEdge.functionArn,
94
+ role: this.serverLambdaForEdge.role,
95
95
  });
96
- this.createFunctionPermissionsForEdge();
97
96
  }
98
97
  else {
99
98
  this.serverLambdaForRegional = this.createFunctionForRegional();
100
- this.createFunctionPermissionsForRegional();
101
99
  }
100
+ this.grantServerS3Permissions();
102
101
  // Create Custom Domain
103
102
  this.validateCustomDomainSettings();
104
103
  this.hostedZone = this.lookupHostedZone();
@@ -114,6 +113,7 @@ export class SsrSite extends Construct {
114
113
  ? this.createCloudFrontDistributionForEdge()
115
114
  : this.createCloudFrontDistributionForRegional();
116
115
  this.distribution.node.addDependency(s3deployCR);
116
+ this.grantServerCloudFrontPermissions();
117
117
  // Invalidate CloudFront
118
118
  this.createCloudFrontInvalidation();
119
119
  // Connect Custom Domain to CloudFront Distribution
@@ -154,7 +154,7 @@ export class SsrSite extends Construct {
154
154
  if (this.doNotDeploy)
155
155
  return;
156
156
  return {
157
- function: this.serverLambdaForEdge || this.serverLambdaForRegional,
157
+ function: this.serverLambdaCdkFunctionForEdge || this.serverLambdaForRegional,
158
158
  bucket: this.bucket,
159
159
  distribution: this.distribution,
160
160
  hostedZone: this.hostedZone,
@@ -174,7 +174,7 @@ export class SsrSite extends Construct {
174
174
  * ```
175
175
  */
176
176
  attachPermissions(permissions) {
177
- const server = this.serverLambdaForEdge ||
177
+ const server = this.serverLambdaCdkFunctionForEdge ||
178
178
  this.serverLambdaForRegional ||
179
179
  this.serverLambdaForDev;
180
180
  attachPermissionsToRole(server?.role, permissions);
@@ -192,7 +192,7 @@ export class SsrSite extends Construct {
192
192
  edge: this.props.edge,
193
193
  server: (this.serverLambdaForDev ||
194
194
  this.serverLambdaForRegional ||
195
- this.serverLambdaForEdge)?.functionArn,
195
+ this.serverLambdaCdkFunctionForEdge)?.functionArn,
196
196
  secrets: (this.props.bind || [])
197
197
  .filter((c) => c instanceof Secret)
198
198
  .map((c) => c.name),
@@ -298,14 +298,20 @@ export class SsrSite extends Construct {
298
298
  : 200;
299
299
  const result = spawn.sync("node", [
300
300
  script,
301
- [
302
- path.join(this.props.path, this.buildConfig.clientBuildOutputDir),
301
+ Buffer.from(JSON.stringify([
302
+ {
303
+ src: path.join(this.props.path, this.buildConfig.clientBuildOutputDir),
304
+ tar: this.buildConfig.clientBuildS3KeyPrefix || "",
305
+ },
303
306
  ...(this.buildConfig.prerenderedBuildOutputDir
304
307
  ? [
305
- path.join(this.props.path, this.buildConfig.prerenderedBuildOutputDir),
308
+ {
309
+ src: path.join(this.props.path, this.buildConfig.prerenderedBuildOutputDir),
310
+ tar: this.buildConfig.prerenderedBuildS3KeyPrefix || "",
311
+ },
306
312
  ]
307
313
  : []),
308
- ].join(","),
314
+ ])).toString("base64"),
309
315
  zipOutDir,
310
316
  `${fileSizeLimit}`,
311
317
  ], {
@@ -452,11 +458,24 @@ export class SsrSite extends Construct {
452
458
  });
453
459
  return ssrFn.function;
454
460
  }
455
- createFunctionPermissionsForRegional() {
456
- this.bucket.grantReadWrite(this.serverLambdaForRegional.role);
461
+ grantServerS3Permissions() {
462
+ const server = this.serverLambdaCdkFunctionForEdge || this.serverLambdaForRegional;
463
+ this.bucket.grantReadWrite(server.role);
457
464
  }
458
- createFunctionPermissionsForEdge() {
459
- this.bucket.grantReadWrite(this.serverLambdaForEdge.role);
465
+ grantServerCloudFrontPermissions() {
466
+ const stack = Stack.of(this);
467
+ const server = this.serverLambdaCdkFunctionForEdge || this.serverLambdaForRegional;
468
+ const policy = new Policy(this, "ServerFunctionInvalidatorPolicy", {
469
+ statements: [
470
+ new PolicyStatement({
471
+ actions: ["cloudfront:CreateInvalidation"],
472
+ resources: [
473
+ `arn:${stack.partition}:cloudfront::${stack.account}:distribution/${this.distribution.distributionId}`,
474
+ ],
475
+ }),
476
+ ],
477
+ });
478
+ server?.role?.attachInlinePolicy(policy);
460
479
  }
461
480
  /////////////////////
462
481
  // CloudFront Distribution
@@ -585,7 +604,7 @@ function handler(event) {
585
604
  {
586
605
  includeBody: true,
587
606
  eventType: LambdaEdgeEventType.ORIGIN_REQUEST,
588
- functionVersion: this.serverEdgeFunction.currentVersion,
607
+ functionVersion: this.serverLambdaForEdge.currentVersion,
589
608
  },
590
609
  ...(cfDistributionProps.defaultBehavior?.edgeLambdas || []),
591
610
  ],
@@ -237,7 +237,13 @@ interface ImportMeta {
237
237
  force: true,
238
238
  recursive: true,
239
239
  });
240
- const cmd = ["node", script, siteOutputPath, zipPath, fileSizeLimit].join(" ");
240
+ const cmd = [
241
+ "node",
242
+ script,
243
+ Buffer.from(JSON.stringify([{ src: siteOutputPath, tar: "" }])).toString("base64"),
244
+ zipPath,
245
+ fileSizeLimit,
246
+ ].join(" ");
241
247
  try {
242
248
  execSync(cmd, {
243
249
  cwd: sitePath,
@@ -273,7 +273,12 @@ export class NextjsSite extends Construct {
273
273
  force: true,
274
274
  recursive: true,
275
275
  });
276
- const result = spawn.sync("node", [script, siteOutputPath, zipPath, `${fileSizeLimit}`], {
276
+ const result = spawn.sync("node", [
277
+ script,
278
+ Buffer.from(JSON.stringify([{ src: siteOutputPath, tar: "" }])).toString("base64"),
279
+ zipPath,
280
+ `${fileSizeLimit}`,
281
+ ], {
277
282
  stdio: "inherit",
278
283
  });
279
284
  if (result.status !== 0) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sideEffects": false,
3
3
  "name": "sst",
4
- "version": "2.13.9",
4
+ "version": "2.14.0",
5
5
  "bin": {
6
6
  "sst": "cli/sst.js"
7
7
  },