sst 2.29.2 → 2.30.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.
- package/cli/commands/types.js +12 -2
- package/constructs/BaseSite.d.ts +12 -3
- package/constructs/Distribution.d.ts +1 -1
- package/constructs/Distribution.js +2 -2
- package/constructs/NextjsSite.d.ts +2 -0
- package/constructs/NextjsSite.js +25 -14
- package/constructs/Script.d.ts +7 -5
- package/constructs/SsrSite.d.ts +97 -8
- package/constructs/SsrSite.js +208 -61
- package/constructs/StaticSite.d.ts +7 -9
- package/constructs/StaticSite.js +20 -15
- package/package.json +2 -2
- package/runtime/handlers.js +7 -30
- package/stacks/synth.js +4 -9
- package/util/semaphore.d.ts +7 -0
- package/util/semaphore.js +27 -0
package/cli/commands/types.js
CHANGED
|
@@ -8,10 +8,20 @@ export const types = (program) => program.command("types", "Generate resource ty
|
|
|
8
8
|
try {
|
|
9
9
|
const project = useProject();
|
|
10
10
|
const [_metafile, sstConfig] = await Stacks.load(project.paths.config);
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
// Note: do not run synth which requires AWS credentials. B/c generating
|
|
12
|
+
// types is usually done inside CI pipelines. And credentials
|
|
13
|
+
// might not be available. ie.
|
|
14
|
+
// await Stacks.synth({
|
|
15
|
+
// fn: sstConfig.stacks,
|
|
16
|
+
// mode: "remove",
|
|
17
|
+
// });
|
|
18
|
+
const app = new App({
|
|
13
19
|
mode: "remove",
|
|
20
|
+
stage: project.config.stage,
|
|
21
|
+
name: project.config.name,
|
|
22
|
+
region: project.config.region,
|
|
14
23
|
});
|
|
24
|
+
sstConfig.stacks(app);
|
|
15
25
|
Colors.line(Colors.success(`✔ `), `Types generated in ${path.resolve(project.paths.out, "types")}`);
|
|
16
26
|
await exit();
|
|
17
27
|
}
|
package/constructs/BaseSite.d.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { ErrorResponse, DistributionProps, BehaviorOptions, IOrigin } from "aws-cdk-lib/aws-cloudfront";
|
|
2
2
|
export interface BaseSiteFileOptions {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
filters: {
|
|
4
|
+
[key in "include" | "exclude"]?: string;
|
|
5
|
+
}[];
|
|
6
|
+
cacheControl?: string;
|
|
6
7
|
contentType?: string;
|
|
8
|
+
contentEncoding?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface BaseSiteFileOptionsDeprecated {
|
|
11
|
+
include?: string | string[];
|
|
12
|
+
exclude?: string | string[];
|
|
13
|
+
cacheControl?: string;
|
|
14
|
+
contentType?: string;
|
|
15
|
+
contentEncoding?: string;
|
|
7
16
|
}
|
|
8
17
|
export interface BaseSiteEnvironmentOutputsInfo {
|
|
9
18
|
path: string;
|
|
@@ -132,7 +132,7 @@ export declare class Distribution extends Construct {
|
|
|
132
132
|
hostedZone: IHostedZone | undefined;
|
|
133
133
|
certificate: ICertificate | undefined;
|
|
134
134
|
};
|
|
135
|
-
createInvalidation(buildId?: string): CustomResource;
|
|
135
|
+
createInvalidation(buildId?: string, paths?: string[]): CustomResource;
|
|
136
136
|
private validateCloudFrontDistributionSettings;
|
|
137
137
|
private validateCustomDomainSettings;
|
|
138
138
|
private lookupHostedZone;
|
|
@@ -70,7 +70,7 @@ export class Distribution extends Construct {
|
|
|
70
70
|
certificate: this.certificate,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
createInvalidation(buildId) {
|
|
73
|
+
createInvalidation(buildId, paths) {
|
|
74
74
|
const stack = Stack.of(this);
|
|
75
75
|
const policy = new Policy(this.scope, "CloudFrontInvalidatorPolicy", {
|
|
76
76
|
statements: [
|
|
@@ -93,7 +93,7 @@ export class Distribution extends Construct {
|
|
|
93
93
|
properties: {
|
|
94
94
|
buildId: buildId || Date.now().toString(),
|
|
95
95
|
distributionId: this.distribution.distributionId,
|
|
96
|
-
paths: ["/*"],
|
|
96
|
+
paths: paths ?? ["/*"],
|
|
97
97
|
waitForInvalidation: this.props.waitForInvalidation,
|
|
98
98
|
},
|
|
99
99
|
});
|
|
@@ -3,6 +3,7 @@ import { Runtime, FunctionProps, Architecture } from "aws-cdk-lib/aws-lambda";
|
|
|
3
3
|
import { SsrSite, SsrSiteNormalizedProps, SsrSiteProps } from "./SsrSite.js";
|
|
4
4
|
import { Size } from "./util/size.js";
|
|
5
5
|
import { Bucket } from "aws-cdk-lib/aws-s3";
|
|
6
|
+
import { CachePolicyProps } from "aws-cdk-lib/aws-cloudfront";
|
|
6
7
|
export interface NextjsSiteProps extends Omit<SsrSiteProps, "nodejs"> {
|
|
7
8
|
imageOptimization?: {
|
|
8
9
|
/**
|
|
@@ -101,6 +102,7 @@ export declare class NextjsSite extends SsrSite {
|
|
|
101
102
|
props: NextjsSiteNormalizedProps;
|
|
102
103
|
private buildId?;
|
|
103
104
|
constructor(scope: Construct, id: string, props?: NextjsSiteProps);
|
|
105
|
+
static buildDefaultServerCachePolicyProps(): CachePolicyProps;
|
|
104
106
|
protected plan(bucket: Bucket): {
|
|
105
107
|
cloudFrontFunctions?: {
|
|
106
108
|
serverCfFunction: {
|
package/constructs/NextjsSite.js
CHANGED
|
@@ -12,6 +12,13 @@ import { toCdkSize } from "./util/size.js";
|
|
|
12
12
|
import { PolicyStatement } from "aws-cdk-lib/aws-iam";
|
|
13
13
|
import { RetentionDays } from "aws-cdk-lib/aws-logs";
|
|
14
14
|
import { VisibleError } from "../error.js";
|
|
15
|
+
const DEFAULT_CACHE_POLICY_ALLOWED_HEADERS = [
|
|
16
|
+
"accept",
|
|
17
|
+
"rsc",
|
|
18
|
+
"next-router-prefetch",
|
|
19
|
+
"next-router-state-tree",
|
|
20
|
+
"next-url",
|
|
21
|
+
];
|
|
15
22
|
/**
|
|
16
23
|
* The `NextjsSite` construct is a higher level CDK construct that makes it easy to create a Next.js app.
|
|
17
24
|
* @example
|
|
@@ -52,6 +59,9 @@ export class NextjsSite extends SsrSite {
|
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
61
|
}
|
|
62
|
+
static buildDefaultServerCachePolicyProps() {
|
|
63
|
+
return super.buildDefaultServerCachePolicyProps(DEFAULT_CACHE_POLICY_ALLOWED_HEADERS);
|
|
64
|
+
}
|
|
55
65
|
plan(bucket) {
|
|
56
66
|
const { path: sitePath, edge, experimental, imageOptimization, } = this.props;
|
|
57
67
|
const serverConfig = {
|
|
@@ -184,13 +194,7 @@ export class NextjsSite extends SsrSite {
|
|
|
184
194
|
origin: "s3",
|
|
185
195
|
})),
|
|
186
196
|
],
|
|
187
|
-
cachePolicyAllowedHeaders:
|
|
188
|
-
"accept",
|
|
189
|
-
"rsc",
|
|
190
|
-
"next-router-prefetch",
|
|
191
|
-
"next-router-state-tree",
|
|
192
|
-
"next-url",
|
|
193
|
-
],
|
|
197
|
+
cachePolicyAllowedHeaders: DEFAULT_CACHE_POLICY_ALLOWED_HEADERS,
|
|
194
198
|
buildId: this.getBuildId(),
|
|
195
199
|
warmerConfig: {
|
|
196
200
|
function: path.join(sitePath, ".open-next", "warmer-function"),
|
|
@@ -287,15 +291,22 @@ export class NextjsSite extends SsrSite {
|
|
|
287
291
|
};
|
|
288
292
|
}
|
|
289
293
|
wrapHandler() {
|
|
290
|
-
const { path: sitePath } = this.props;
|
|
294
|
+
const { path: sitePath, experimental } = this.props;
|
|
291
295
|
const wrapperName = "nextjssite-index";
|
|
292
296
|
const serverPath = path.join(sitePath, ".open-next", "server-function");
|
|
293
|
-
fs.writeFileSync(path.join(serverPath, `${wrapperName}.mjs`),
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
fs.writeFileSync(path.join(serverPath, `${wrapperName}.mjs`), experimental?.streaming
|
|
298
|
+
? [
|
|
299
|
+
`export const handler = awslambda.streamifyResponse(async (...args) => {`,
|
|
300
|
+
` const { handler: rawHandler} = await import("./index.mjs");`,
|
|
301
|
+
` return rawHandler(...args);`,
|
|
302
|
+
`});`,
|
|
303
|
+
].join("\n")
|
|
304
|
+
: [
|
|
305
|
+
`export const handler = async (...args) => {`,
|
|
306
|
+
` const { handler: rawHandler} = await import("./index.mjs");`,
|
|
307
|
+
` return rawHandler(...args);`,
|
|
308
|
+
`};`,
|
|
309
|
+
].join("\n"));
|
|
299
310
|
return `${wrapperName}.handler`;
|
|
300
311
|
}
|
|
301
312
|
getRoutes() {
|
package/constructs/Script.d.ts
CHANGED
|
@@ -51,8 +51,7 @@ export interface ScriptProps {
|
|
|
51
51
|
function?: FunctionProps;
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* Specifies the function to be run once when the Script construct is created.
|
|
56
55
|
* @example
|
|
57
56
|
* ```js
|
|
58
57
|
* new Script(stack, "Api", {
|
|
@@ -62,8 +61,11 @@ export interface ScriptProps {
|
|
|
62
61
|
*/
|
|
63
62
|
onCreate?: FunctionDefinition;
|
|
64
63
|
/**
|
|
65
|
-
*
|
|
64
|
+
* Specifies the function to be run each time the Script construct is redeployed. If a version is provided,
|
|
65
|
+
* the function is only executed when the version changes.
|
|
66
66
|
*
|
|
67
|
+
* Note that the `onUpdate` function is not run during the initial creation of the Script construct.
|
|
68
|
+
* For initial creation, use `onCreate`.
|
|
67
69
|
* @example
|
|
68
70
|
* ```js
|
|
69
71
|
* new Script(stack, "Api", {
|
|
@@ -73,8 +75,8 @@ export interface ScriptProps {
|
|
|
73
75
|
*/
|
|
74
76
|
onUpdate?: FunctionDefinition;
|
|
75
77
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
+
* Specifies the function to be run once when the Script construct is deleted from the stack or
|
|
79
|
+
* when the entire stack is removed from the app.
|
|
78
80
|
* @example
|
|
79
81
|
* ```js
|
|
80
82
|
* new Script(stack, "Api", {
|
package/constructs/SsrSite.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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, ErrorResponse } from "aws-cdk-lib/aws-cloudfront";
|
|
4
|
+
import { ICachePolicy, IResponseHeadersPolicy, 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";
|
|
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, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
|
|
11
|
+
import { BaseSiteFileOptions, BaseSiteFileOptionsDeprecated, 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";
|
|
@@ -54,6 +54,8 @@ export interface SsrDomainProps extends DistributionDomainProps {
|
|
|
54
54
|
}
|
|
55
55
|
export interface SsrSiteFileOptions extends BaseSiteFileOptions {
|
|
56
56
|
}
|
|
57
|
+
export interface SsrSiteFileOptionsDeprecated extends BaseSiteFileOptionsDeprecated {
|
|
58
|
+
}
|
|
57
59
|
export interface SsrSiteReplaceProps extends BaseSiteReplaceProps {
|
|
58
60
|
}
|
|
59
61
|
export interface SsrCdkDistributionProps extends BaseSiteCdkDistributionProps {
|
|
@@ -202,6 +204,93 @@ export interface SsrSiteProps {
|
|
|
202
204
|
*/
|
|
203
205
|
url?: string;
|
|
204
206
|
};
|
|
207
|
+
cache?: {
|
|
208
|
+
/**
|
|
209
|
+
* 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.
|
|
210
|
+
* @default utf-8
|
|
211
|
+
* @example
|
|
212
|
+
* ```js
|
|
213
|
+
* cache: {
|
|
214
|
+
* textEncoding: "iso-8859-1"
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
textEncoding?: "UTF-8" | "ISO-8859-1" | "Windows-1252" | "ASCII" | "none";
|
|
219
|
+
/**
|
|
220
|
+
* 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.
|
|
221
|
+
* - "never" - No invalidation will be performed.
|
|
222
|
+
* - "all" - All files will be invalidated when any file changes. (Default, requires checking file content which will increase deployment time)
|
|
223
|
+
* - "versioned" - Only versioned files will be invalidated when versioned files change.
|
|
224
|
+
* - "always" - All files are invalidated on every deployment.
|
|
225
|
+
* @default all
|
|
226
|
+
* @example
|
|
227
|
+
* ```js
|
|
228
|
+
* cache: {
|
|
229
|
+
* cdnInvalidationStrategy: "versioned"
|
|
230
|
+
* }
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
cdnInvalidationStrategy?: "never" | "all" | "versioned" | "always";
|
|
234
|
+
/**
|
|
235
|
+
* The TTL for versioned files (ex: `main-1234.css`) in the CDN and browser cache. Ignored when `versionedFilesCacheHeader` is specified.
|
|
236
|
+
* @default 1 year
|
|
237
|
+
* @example
|
|
238
|
+
* ```js
|
|
239
|
+
* cache: {
|
|
240
|
+
* versionedFilesTTL: "30 days"
|
|
241
|
+
* }
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
versionedFilesTTL?: number | Duration;
|
|
245
|
+
/**
|
|
246
|
+
* The header to use for versioned files (ex: `main-1234.css`) in the CDN cache. When specified, the `versionedFilesTTL` option is ignored.
|
|
247
|
+
* @default public,max-age=31536000,immutable
|
|
248
|
+
* @example
|
|
249
|
+
* ```js
|
|
250
|
+
* cache: {
|
|
251
|
+
* versionedFilesCacheHeader: "public,max-age=31536000,immutable"
|
|
252
|
+
* }
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
versionedFilesCacheHeader?: string;
|
|
256
|
+
/**
|
|
257
|
+
* The TTL for non-versioned files (ex: `index.html`) in the CDN cache. Ignored when `nonVersionedFilesCacheHeader` is specified.
|
|
258
|
+
* @default 1 day
|
|
259
|
+
* @example
|
|
260
|
+
* ```js
|
|
261
|
+
* cache: {
|
|
262
|
+
* nonVersionedFilesTTL: "4 hours"
|
|
263
|
+
* }
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
nonVersionedFilesTTL?: number | Duration;
|
|
267
|
+
/**
|
|
268
|
+
* The header to use for non-versioned files (ex: `index.html`) in the CDN cache. When specified, the `nonVersionedFilesTTL` option is ignored.
|
|
269
|
+
* @default public,max-age=0,s-maxage=86400,stale-while-revalidate=8640
|
|
270
|
+
* @example
|
|
271
|
+
* ```js
|
|
272
|
+
* cache: {
|
|
273
|
+
* nonVersionedFilesCacheHeader: "public,max-age=0,no-cache"
|
|
274
|
+
* }
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
nonVersionedFilesCacheHeader?: string;
|
|
278
|
+
/**
|
|
279
|
+
* 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.
|
|
280
|
+
* @example
|
|
281
|
+
* ```js
|
|
282
|
+
* cache: {
|
|
283
|
+
* fileOptions: [
|
|
284
|
+
* {
|
|
285
|
+
* filters: [ { exclude: "*" }, { include: "*.zip" } ],
|
|
286
|
+
* cacheControl: "private,no-cache,no-store,must-revalidate",
|
|
287
|
+
* contentType: "application/zip",
|
|
288
|
+
* },
|
|
289
|
+
* ],
|
|
290
|
+
* }
|
|
291
|
+
*/
|
|
292
|
+
fileOptions?: SsrSiteFileOptions[];
|
|
293
|
+
};
|
|
205
294
|
/**
|
|
206
295
|
* 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.
|
|
207
296
|
* @default false
|
|
@@ -251,11 +340,11 @@ export interface SsrSiteProps {
|
|
|
251
340
|
server?: Pick<CdkFunctionProps, "layers" | "vpc" | "vpcSubnets" | "securityGroups" | "allowAllOutbound" | "allowPublicSubnet" | "architecture" | "logRetention"> & Pick<FunctionProps, "copyFiles">;
|
|
252
341
|
};
|
|
253
342
|
/**
|
|
254
|
-
* Pass in a list of file options to customize cache control and content type specific files.
|
|
255
|
-
*
|
|
343
|
+
* 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.
|
|
344
|
+
* @deprecated Use `cache.fileOptions` instead. Note that the `cache.fileOptions` are appended to default file options, not a replacement as this prop was.
|
|
256
345
|
* @default
|
|
257
|
-
* Versioned files cached for 1 year at the CDN and
|
|
258
|
-
*
|
|
346
|
+
* Versioned files cached for 1 year at the CDN and browser level.
|
|
347
|
+
* Nonversioned files cached for 1 day at the CDN level, but not at the browser level.
|
|
259
348
|
* ```js
|
|
260
349
|
* fileOptions: [
|
|
261
350
|
* {
|
|
@@ -301,9 +390,8 @@ export interface SsrSiteProps {
|
|
|
301
390
|
* cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
|
|
302
391
|
* },
|
|
303
392
|
* ]
|
|
304
|
-
* ```
|
|
305
393
|
*/
|
|
306
|
-
fileOptions?:
|
|
394
|
+
fileOptions?: SsrSiteFileOptionsDeprecated[];
|
|
307
395
|
}
|
|
308
396
|
export type SsrSiteNormalizedProps = SsrSiteProps & {
|
|
309
397
|
path: Exclude<SsrSiteProps["path"], undefined>;
|
|
@@ -333,6 +421,7 @@ export declare abstract class SsrSite extends Construct implements SSTConstruct
|
|
|
333
421
|
private serverFunctionForDev?;
|
|
334
422
|
private distribution;
|
|
335
423
|
constructor(scope: Construct, id: string, rawProps?: SsrSiteProps);
|
|
424
|
+
protected static buildDefaultServerCachePolicyProps(allowedHeaders: string[]): CachePolicyProps;
|
|
336
425
|
/**
|
|
337
426
|
* The CloudFront URL of the website.
|
|
338
427
|
*/
|
package/constructs/SsrSite.js
CHANGED
|
@@ -67,7 +67,7 @@ export class SsrSite extends Construct {
|
|
|
67
67
|
const app = scope.node.root;
|
|
68
68
|
const stack = Stack.of(this);
|
|
69
69
|
const self = this;
|
|
70
|
-
const { path: sitePath, typesPath, buildCommand, runtime, timeout, memorySize, edge, regional, dev, nodejs, permissions, environment, bind, customDomain, waitForInvalidation, fileOptions, warm, cdk, } = props;
|
|
70
|
+
const { path: sitePath, typesPath, buildCommand, runtime, timeout, memorySize, edge, regional, dev, cache, nodejs, permissions, environment, bind, customDomain, waitForInvalidation, fileOptions, warm, cdk, } = props;
|
|
71
71
|
this.doNotDeploy = !stack.isActive || (app.mode === "dev" && !dev?.deploy);
|
|
72
72
|
validateSiteExists();
|
|
73
73
|
validateTimeout();
|
|
@@ -96,7 +96,7 @@ export class SsrSite extends Construct {
|
|
|
96
96
|
const edgeFunctions = createEdgeFunctions();
|
|
97
97
|
const origins = createOrigins();
|
|
98
98
|
const distribution = createCloudFrontDistribution();
|
|
99
|
-
|
|
99
|
+
createDistributionInvalidation();
|
|
100
100
|
// Create Warmer
|
|
101
101
|
createWarmer();
|
|
102
102
|
this.bucket = bucket;
|
|
@@ -411,7 +411,7 @@ function handler(event) {
|
|
|
411
411
|
originPath: "/" + (props.originPath ?? ""),
|
|
412
412
|
});
|
|
413
413
|
const assets = createS3OriginAssets(props.copy);
|
|
414
|
-
const assetFileOptions = fileOptions || createS3OriginAssetFileOptions(props.copy);
|
|
414
|
+
const assetFileOptions = fileOptions || createS3OriginAssetFileOptions(props.copy, cache);
|
|
415
415
|
const s3deployCR = createS3OriginDeployment(assets, assetFileOptions);
|
|
416
416
|
s3DeployCRs.push(s3deployCR);
|
|
417
417
|
return s3Origin;
|
|
@@ -562,27 +562,122 @@ function handler(event) {
|
|
|
562
562
|
}
|
|
563
563
|
return assets;
|
|
564
564
|
}
|
|
565
|
-
function createS3OriginAssetFileOptions(copy) {
|
|
565
|
+
function createS3OriginAssetFileOptions(copy, cache) {
|
|
566
566
|
const fileOptions = [];
|
|
567
|
-
|
|
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) => {
|
|
568
610
|
if (!files.cached)
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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) {
|
|
574
666
|
fileOptions.push({
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
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,
|
|
584
676
|
});
|
|
585
677
|
}
|
|
678
|
+
});
|
|
679
|
+
if (cache?.fileOptions) {
|
|
680
|
+
fileOptions.push(...cache.fileOptions);
|
|
586
681
|
}
|
|
587
682
|
return fileOptions;
|
|
588
683
|
}
|
|
@@ -622,18 +717,25 @@ function handler(event) {
|
|
|
622
717
|
ObjectKey: asset.s3ObjectKey,
|
|
623
718
|
})),
|
|
624
719
|
DestinationBucketName: bucket.bucketName,
|
|
625
|
-
FileOptions: (fileOptions || []).map((
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
if (typeof include === "string") {
|
|
630
|
-
include = [include];
|
|
631
|
-
}
|
|
720
|
+
FileOptions: (fileOptions || []).map((o) => {
|
|
721
|
+
const { filters, cacheControl, contentType, contentEncoding } = o;
|
|
722
|
+
const { include, exclude } = o;
|
|
632
723
|
return [
|
|
633
|
-
...
|
|
634
|
-
|
|
635
|
-
|
|
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] : [],
|
|
636
737
|
contentType ? ["--content-type", contentType] : [],
|
|
738
|
+
contentEncoding ? ["--content-encoding", contentEncoding] : [],
|
|
637
739
|
].flat();
|
|
638
740
|
}),
|
|
639
741
|
ReplaceValues: getS3ContentReplaceValues(),
|
|
@@ -656,19 +758,7 @@ function handler(event) {
|
|
|
656
758
|
const allowedHeaders = plan.cachePolicyAllowedHeaders || [];
|
|
657
759
|
singletonCachePolicy =
|
|
658
760
|
singletonCachePolicy ??
|
|
659
|
-
new CachePolicy(self, "ServerCache",
|
|
660
|
-
queryStringBehavior: CacheQueryStringBehavior.all(),
|
|
661
|
-
headerBehavior: allowedHeaders.length > 0
|
|
662
|
-
? CacheHeaderBehavior.allowList(...allowedHeaders)
|
|
663
|
-
: CacheHeaderBehavior.none(),
|
|
664
|
-
cookieBehavior: CacheCookieBehavior.none(),
|
|
665
|
-
defaultTtl: CdkDuration.days(0),
|
|
666
|
-
maxTtl: CdkDuration.days(365),
|
|
667
|
-
minTtl: CdkDuration.days(0),
|
|
668
|
-
enableAcceptEncodingBrotli: true,
|
|
669
|
-
enableAcceptEncodingGzip: true,
|
|
670
|
-
comment: "SST server response cache policy",
|
|
671
|
-
});
|
|
761
|
+
new CachePolicy(self, "ServerCache", SsrSite.buildDefaultServerCachePolicyProps(allowedHeaders));
|
|
672
762
|
return singletonCachePolicy;
|
|
673
763
|
}
|
|
674
764
|
function useServerBehaviorOriginRequestPolicy() {
|
|
@@ -705,34 +795,91 @@ function handler(event) {
|
|
|
705
795
|
});
|
|
706
796
|
return replaceValues;
|
|
707
797
|
}
|
|
708
|
-
function
|
|
798
|
+
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
|
+
}
|
|
709
811
|
// We will generate a hash based on the contents of the S3 files with cache enabled.
|
|
710
812
|
// This will be used to determine if we need to invalidate our CloudFront cache.
|
|
711
813
|
const s3Origin = Object.values(plan.origins).find((origin) => origin.type === "s3");
|
|
712
814
|
if (s3Origin?.type !== "s3")
|
|
713
|
-
return
|
|
714
|
-
const cachedS3Files = s3Origin.copy.
|
|
715
|
-
if (
|
|
716
|
-
return
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
};
|
|
726
|
-
const files = glob.sync("**", globOptions);
|
|
727
|
-
const hash = crypto.createHash("sha1");
|
|
728
|
-
for (const file of files) {
|
|
729
|
-
hash.update(file);
|
|
815
|
+
return;
|
|
816
|
+
const cachedS3Files = s3Origin.copy.filter((file) => file.cached);
|
|
817
|
+
if (cachedS3Files.length === 0)
|
|
818
|
+
return;
|
|
819
|
+
// Build invalidation paths
|
|
820
|
+
const invalidationPaths = [];
|
|
821
|
+
if (cdnInvalidationStrategy === "versioned") {
|
|
822
|
+
cachedS3Files.forEach((item) => {
|
|
823
|
+
if (!item.versionedSubDir)
|
|
824
|
+
return;
|
|
825
|
+
invalidationPaths.push(path.posix.join("/", item.to, item.versionedSubDir, "*"));
|
|
826
|
+
});
|
|
730
827
|
}
|
|
828
|
+
if (invalidationPaths.length === 0)
|
|
829
|
+
invalidationPaths.push("/*");
|
|
830
|
+
// 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
|
+
}
|
|
862
|
+
});
|
|
731
863
|
const buildId = hash.digest("hex");
|
|
732
864
|
Logger.debug(`Generated build ID ${buildId}`);
|
|
733
|
-
|
|
865
|
+
distribution.createInvalidation(buildId, invalidationPaths);
|
|
734
866
|
}
|
|
735
867
|
}
|
|
868
|
+
static buildDefaultServerCachePolicyProps(allowedHeaders) {
|
|
869
|
+
return {
|
|
870
|
+
queryStringBehavior: CacheQueryStringBehavior.all(),
|
|
871
|
+
headerBehavior: allowedHeaders.length > 0
|
|
872
|
+
? CacheHeaderBehavior.allowList(...allowedHeaders)
|
|
873
|
+
: CacheHeaderBehavior.none(),
|
|
874
|
+
cookieBehavior: CacheCookieBehavior.none(),
|
|
875
|
+
defaultTtl: CdkDuration.days(0),
|
|
876
|
+
maxTtl: CdkDuration.days(365),
|
|
877
|
+
minTtl: CdkDuration.days(0),
|
|
878
|
+
enableAcceptEncodingBrotli: true,
|
|
879
|
+
enableAcceptEncodingGzip: true,
|
|
880
|
+
comment: "SST server response cache policy",
|
|
881
|
+
};
|
|
882
|
+
}
|
|
736
883
|
/**
|
|
737
884
|
* The CloudFront URL of the website.
|
|
738
885
|
*/
|
|
@@ -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, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
|
|
5
|
+
import { BaseSiteFileOptions, BaseSiteReplaceProps, BaseSiteCdkDistributionProps, BaseSiteFileOptionsDeprecated } from "./BaseSite.js";
|
|
6
6
|
import { SSTConstruct } from "./Construct.js";
|
|
7
7
|
import { FunctionBindingProps } from "./util/functionBinding.js";
|
|
8
8
|
export interface StaticSiteProps {
|
|
@@ -69,18 +69,15 @@ export interface StaticSiteProps {
|
|
|
69
69
|
buildOutput?: string;
|
|
70
70
|
/**
|
|
71
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
|
-
*
|
|
73
72
|
* @default No cache control for HTML files, and a 1 year cache control for JS/CSS files.
|
|
74
73
|
* ```js
|
|
75
74
|
* [
|
|
76
75
|
* {
|
|
77
|
-
* exclude: "*",
|
|
78
|
-
* include: "*.html",
|
|
76
|
+
* filters: [{ exclude: "*" }, { include: "*.html" }],
|
|
79
77
|
* cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
|
|
80
78
|
* },
|
|
81
79
|
* {
|
|
82
|
-
* exclude: "*",
|
|
83
|
-
* include: ["*.js", "*.css"],
|
|
80
|
+
* filters: [{ exclude: "*" }, { include: "*.js" }, { include: "*.css" }],
|
|
84
81
|
* cacheControl: "max-age=31536000,public,immutable",
|
|
85
82
|
* },
|
|
86
83
|
* ]
|
|
@@ -90,14 +87,13 @@ export interface StaticSiteProps {
|
|
|
90
87
|
* new StaticSite(stack, "Site", {
|
|
91
88
|
* buildOutput: "dist",
|
|
92
89
|
* fileOptions: [{
|
|
93
|
-
* exclude: "*",
|
|
94
|
-
* include: "*.js",
|
|
90
|
+
* filters: [{ exclude: "*" }, { include: "*.js" }],
|
|
95
91
|
* cacheControl: "max-age=31536000,public,immutable",
|
|
96
92
|
* }]
|
|
97
93
|
* });
|
|
98
94
|
* ```
|
|
99
95
|
*/
|
|
100
|
-
fileOptions?: StaticSiteFileOptions[];
|
|
96
|
+
fileOptions?: StaticSiteFileOptions[] | StaticSiteFileOptionsDeprecated[];
|
|
101
97
|
/**
|
|
102
98
|
* Pass in a list of placeholder values to be replaced in the website content. For example, the follow configuration:
|
|
103
99
|
*
|
|
@@ -269,6 +265,8 @@ export interface StaticSiteDomainProps extends DistributionDomainProps {
|
|
|
269
265
|
}
|
|
270
266
|
export interface StaticSiteFileOptions extends BaseSiteFileOptions {
|
|
271
267
|
}
|
|
268
|
+
export interface StaticSiteFileOptionsDeprecated extends BaseSiteFileOptionsDeprecated {
|
|
269
|
+
}
|
|
272
270
|
export interface StaticSiteReplaceProps extends BaseSiteReplaceProps {
|
|
273
271
|
}
|
|
274
272
|
export interface StaticSiteCdkDistributionProps extends BaseSiteCdkDistributionProps {
|
package/constructs/StaticSite.js
CHANGED
|
@@ -296,15 +296,13 @@ interface ImportMeta {
|
|
|
296
296
|
}
|
|
297
297
|
}
|
|
298
298
|
createS3Deployment(cliLayer, assets, filenamesAsset) {
|
|
299
|
-
const fileOptions = this.props.fileOptions
|
|
299
|
+
const fileOptions = this.props.fileOptions ?? [
|
|
300
300
|
{
|
|
301
|
-
exclude: "*",
|
|
302
|
-
include: "*.html",
|
|
301
|
+
filters: [{ exclude: "*" }, { include: "*.html" }],
|
|
303
302
|
cacheControl: "max-age=0,no-cache,no-store,must-revalidate",
|
|
304
303
|
},
|
|
305
304
|
{
|
|
306
|
-
exclude: "*",
|
|
307
|
-
include: ["*.js", "*.css"],
|
|
305
|
+
filters: [{ exclude: "*" }, { include: "*.js" }, { include: "*.css" }],
|
|
308
306
|
cacheControl: "max-age=31536000,public,immutable",
|
|
309
307
|
},
|
|
310
308
|
];
|
|
@@ -348,18 +346,25 @@ interface ImportMeta {
|
|
|
348
346
|
BucketName: filenamesAsset.s3BucketName,
|
|
349
347
|
ObjectKey: filenamesAsset.s3ObjectKey,
|
|
350
348
|
},
|
|
351
|
-
FileOptions: (fileOptions || []).map((
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
if (typeof include === "string") {
|
|
356
|
-
include = [include];
|
|
357
|
-
}
|
|
349
|
+
FileOptions: (fileOptions || []).map((o) => {
|
|
350
|
+
const { filters, cacheControl, contentType, contentEncoding } = o;
|
|
351
|
+
const { include, exclude } = o;
|
|
358
352
|
return [
|
|
359
|
-
...
|
|
360
|
-
|
|
361
|
-
|
|
353
|
+
...(typeof exclude === "string" ? [exclude] : exclude ?? [])
|
|
354
|
+
.map((entry) => ["--exclude", entry])
|
|
355
|
+
.flat(2),
|
|
356
|
+
...(typeof include === "string" ? [include] : include ?? [])
|
|
357
|
+
.map((entry) => ["--include", entry])
|
|
358
|
+
.flat(2),
|
|
359
|
+
...(filters || [])
|
|
360
|
+
.map((filter) => Object.entries(filter).map(([key, value]) => [
|
|
361
|
+
`--${key}`,
|
|
362
|
+
value,
|
|
363
|
+
]))
|
|
364
|
+
.flat(2),
|
|
365
|
+
cacheControl ? ["--cache-control", cacheControl] : [],
|
|
362
366
|
contentType ? ["--content-type", contentType] : [],
|
|
367
|
+
contentEncoding ? ["--content-encoding", contentEncoding] : [],
|
|
363
368
|
].flat();
|
|
364
369
|
}),
|
|
365
370
|
ReplaceValues: this.getS3ContentReplaceValues(),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sideEffects": false,
|
|
3
3
|
"name": "sst",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.30.1",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sst": "cli/sst.js"
|
|
7
7
|
},
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"@types/ws": "^8.5.3",
|
|
120
120
|
"@types/yargs": "^17.0.13",
|
|
121
121
|
"archiver": "^5.3.1",
|
|
122
|
-
"astro-sst": "2.
|
|
122
|
+
"astro-sst": "2.30.1",
|
|
123
123
|
"tsx": "^3.12.1",
|
|
124
124
|
"typescript": "^5.2.2",
|
|
125
125
|
"vitest": "^0.33.0"
|
package/runtime/handlers.js
CHANGED
|
@@ -13,6 +13,7 @@ import { useJavaHandler } from "./handlers/java.js";
|
|
|
13
13
|
import { usePythonHandler } from "./handlers/python.js";
|
|
14
14
|
import { useRustHandler } from "./handlers/rust.js";
|
|
15
15
|
import { lazy } from "../util/lazy.js";
|
|
16
|
+
import { Semaphore } from "../util/semaphore.js";
|
|
16
17
|
export const useRuntimeHandlers = lazy(() => {
|
|
17
18
|
const handlers = [
|
|
18
19
|
useNodeHandler(),
|
|
@@ -104,9 +105,12 @@ export const useRuntimeHandlers = lazy(() => {
|
|
|
104
105
|
const promise = task();
|
|
105
106
|
pendingBuilds.set(functionID, promise);
|
|
106
107
|
Logger.debug("Building function", functionID);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
try {
|
|
109
|
+
return await promise;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
pendingBuilds.delete(functionID);
|
|
113
|
+
}
|
|
110
114
|
},
|
|
111
115
|
};
|
|
112
116
|
return result;
|
|
@@ -156,30 +160,3 @@ export const useFunctionBuilder = lazy(() => {
|
|
|
156
160
|
});
|
|
157
161
|
return result;
|
|
158
162
|
});
|
|
159
|
-
class Semaphore {
|
|
160
|
-
queue = [];
|
|
161
|
-
locked = 0;
|
|
162
|
-
maxLocks;
|
|
163
|
-
constructor(maxLocks = 1) {
|
|
164
|
-
this.maxLocks = maxLocks;
|
|
165
|
-
}
|
|
166
|
-
lock() {
|
|
167
|
-
return new Promise((resolve) => {
|
|
168
|
-
const unlock = () => {
|
|
169
|
-
this.locked--;
|
|
170
|
-
const next = this.queue.shift();
|
|
171
|
-
if (next) {
|
|
172
|
-
this.locked++;
|
|
173
|
-
next(unlock);
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
if (this.locked < this.maxLocks) {
|
|
177
|
-
this.locked++;
|
|
178
|
-
resolve(unlock);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
this.queue.push(unlock);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
package/stacks/synth.js
CHANGED
|
@@ -5,6 +5,8 @@ import * as contextproviders from "sst-aws-cdk/lib/context-providers/index.js";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { VisibleError } from "../error.js";
|
|
7
7
|
import fs from "fs/promises";
|
|
8
|
+
import { Semaphore } from "../util/semaphore.js";
|
|
9
|
+
const sem = new Semaphore(1);
|
|
8
10
|
export async function synth(opts) {
|
|
9
11
|
Logger.debug("Synthesizing stacks...");
|
|
10
12
|
const { App } = await import("../constructs/App.js");
|
|
@@ -13,6 +15,7 @@ export async function synth(opts) {
|
|
|
13
15
|
const project = useProject();
|
|
14
16
|
const cwd = process.cwd();
|
|
15
17
|
process.chdir(project.paths.root);
|
|
18
|
+
const unlock = await sem.lock();
|
|
16
19
|
try {
|
|
17
20
|
return await synthInRoot();
|
|
18
21
|
}
|
|
@@ -20,6 +23,7 @@ export async function synth(opts) {
|
|
|
20
23
|
throw e;
|
|
21
24
|
}
|
|
22
25
|
finally {
|
|
26
|
+
unlock();
|
|
23
27
|
process.chdir(cwd);
|
|
24
28
|
}
|
|
25
29
|
async function synthInRoot() {
|
|
@@ -30,15 +34,6 @@ export async function synth(opts) {
|
|
|
30
34
|
};
|
|
31
35
|
await fs.rm(opts.buildDir, { recursive: true, force: true });
|
|
32
36
|
await fs.mkdir(opts.buildDir, { recursive: true });
|
|
33
|
-
/*
|
|
34
|
-
console.log(JSON.stringify(cfg.context));
|
|
35
|
-
const executable = new CloudExecutable({
|
|
36
|
-
sdkProvider: await useAWSProvider(),
|
|
37
|
-
configuration: cfg,
|
|
38
|
-
synthesizer: async () => app.synth() as any
|
|
39
|
-
});
|
|
40
|
-
const { assembly } = await executable.synthesize(true);
|
|
41
|
-
*/
|
|
42
37
|
const cfg = new Configuration();
|
|
43
38
|
await cfg.load();
|
|
44
39
|
let previous = new Set();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class Semaphore {
|
|
2
|
+
queue = [];
|
|
3
|
+
locked = 0;
|
|
4
|
+
maxLocks;
|
|
5
|
+
constructor(maxLocks = 1) {
|
|
6
|
+
this.maxLocks = maxLocks;
|
|
7
|
+
}
|
|
8
|
+
lock() {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const unlock = () => {
|
|
11
|
+
this.locked--;
|
|
12
|
+
const next = this.queue.shift();
|
|
13
|
+
if (next) {
|
|
14
|
+
this.locked++;
|
|
15
|
+
next(unlock);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
if (this.locked < this.maxLocks) {
|
|
19
|
+
this.locked++;
|
|
20
|
+
resolve(unlock);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
this.queue.push(unlock);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|