sst 2.21.7 → 2.22.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.
package/bootstrap.js CHANGED
@@ -5,7 +5,7 @@ import { spawn } from "child_process";
5
5
  import { DescribeStacksCommand, CloudFormationClient, } from "@aws-sdk/client-cloudformation";
6
6
  import { App, DefaultStackSynthesizer, Duration, CfnOutput, Tags, Stack, RemovalPolicy, } from "aws-cdk-lib/core";
7
7
  import { Function, Runtime, Code } from "aws-cdk-lib/aws-lambda";
8
- import { PolicyStatement } from "aws-cdk-lib/aws-iam";
8
+ import { ManagedPolicy, PermissionsBoundary, PolicyStatement, } from "aws-cdk-lib/aws-iam";
9
9
  import { Rule } from "aws-cdk-lib/aws-events";
10
10
  import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
11
11
  import { BlockPublicAccess, Bucket, BucketEncryption, } from "aws-cdk-lib/aws-s3";
@@ -29,10 +29,12 @@ export const useBootstrap = Context.memo(async () => {
29
29
  loadSSTStatus(),
30
30
  ]);
31
31
  Logger.debug("Loaded bootstrap status");
32
- const needToBootstrapCDK = !cdkStatus;
33
- const needToBootstrapSST = !sstStatus;
32
+ const needToBootstrapCDK = cdkStatus.status !== "ready";
33
+ const needToBootstrapSST = sstStatus.status !== "ready";
34
34
  if (needToBootstrapCDK || needToBootstrapSST) {
35
- const spinner = createSpinner("Deploying bootstrap stack, this only needs to happen once").start();
35
+ const spinner = createSpinner(cdkStatus.status === "bootstrap" || sstStatus.status === "bootstrap"
36
+ ? "Deploying bootstrap stack, this only needs to happen once"
37
+ : "Updating bootstrap stack").start();
36
38
  if (needToBootstrapCDK) {
37
39
  await bootstrapCDK();
38
40
  }
@@ -40,7 +42,7 @@ export const useBootstrap = Context.memo(async () => {
40
42
  await bootstrapSST();
41
43
  // fetch bootstrap status
42
44
  sstStatus = await loadSSTStatus();
43
- if (!sstStatus)
45
+ if (sstStatus.status !== "ready")
44
46
  throw new VisibleError("Failed to load bootstrap stack status");
45
47
  }
46
48
  spinner.succeed();
@@ -56,24 +58,25 @@ async function loadCDKStatus() {
56
58
  const { Stacks: stacks } = await client.send(new DescribeStacksCommand({ StackName: stackName }));
57
59
  // Check CDK bootstrap stack exists
58
60
  if (!stacks || stacks.length === 0)
59
- return false;
61
+ return { status: "bootstrap" };
60
62
  // Check CDK bootstrap stack deployed successfully
61
63
  if (!["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(stacks[0].StackStatus)) {
62
- return false;
64
+ return { status: "bootstrap" };
63
65
  }
64
66
  // Check CDK bootstrap stack is up to date
65
67
  // note: there is no a programmatical way to get the minimal required version
66
68
  // of CDK bootstrap stack. We are going to hardcode it to 14 for now,
67
69
  // which is the latest version as of CDK v2.62.2
68
70
  const output = stacks[0].Outputs?.find((o) => o.OutputKey === "BootstrapVersion");
69
- if (!output || parseInt(output.OutputValue) < 14)
70
- return false;
71
- return true;
71
+ if (!output || parseInt(output.OutputValue) < 14) {
72
+ return { status: "update" };
73
+ }
74
+ return { status: "ready" };
72
75
  }
73
76
  catch (e) {
74
77
  if (e.name === "ValidationError" &&
75
78
  e.message === `Stack with id ${stackName} does not exist`) {
76
- return false;
79
+ return { status: "bootstrap" };
77
80
  }
78
81
  else {
79
82
  throw e;
@@ -94,7 +97,7 @@ async function loadSSTStatus() {
94
97
  catch (e) {
95
98
  if (e.Code === "ValidationError" &&
96
99
  e.message === `Stack with id ${stackName} does not exist`) {
97
- return null;
100
+ return { status: "bootstrap" };
98
101
  }
99
102
  throw e;
100
103
  }
@@ -110,7 +113,7 @@ async function loadSSTStatus() {
110
113
  }
111
114
  });
112
115
  if (!version || !bucket) {
113
- return null;
116
+ return { status: "bootstrap" };
114
117
  }
115
118
  // Need to update bootstrap stack:
116
119
  // 1. If current MAJOR version < latest MAJOR version
@@ -126,9 +129,9 @@ async function loadSSTStatus() {
126
129
  if (currentMajor < latestMajor ||
127
130
  currentMajor > latestMajor ||
128
131
  currentMinor < latestMinor) {
129
- return null;
132
+ return { status: "update" };
130
133
  }
131
- return { version, bucket };
134
+ return { status: "ready", version, bucket };
132
135
  }
133
136
  export async function bootstrapSST() {
134
137
  const { region, bootstrap, cdk } = useProject().config;
@@ -215,6 +218,11 @@ export async function bootstrapSST() {
215
218
  },
216
219
  });
217
220
  rule.addTarget(new LambdaFunction(fn));
221
+ // Create permissions boundary
222
+ if (cdk?.customPermissionsBoundary) {
223
+ const boundaryPolicy = ManagedPolicy.fromManagedPolicyName(stack, "PermissionBoundaryPolicy", cdk.customPermissionsBoundary);
224
+ PermissionsBoundary.of(stack).apply(boundaryPolicy);
225
+ }
218
226
  // Create stack outputs to store bootstrap stack info
219
227
  new CfnOutput(stack, OUTPUT_VERSION, { value: LATEST_VERSION });
220
228
  new CfnOutput(stack, OUTPUT_BUCKET, { value: bucket.bucketName });
@@ -104,11 +104,38 @@ export async function useLocalServer(opts) {
104
104
  const wss = new WebSocketServer({ noServer: true });
105
105
  const wss2 = new WebSocketServer({ noServer: true });
106
106
  const sockets = new Set();
107
+ let buffer = [
108
+ {
109
+ type: "cli.dev",
110
+ properties: {
111
+ stage: project.config.stage,
112
+ app: project.config.name,
113
+ },
114
+ },
115
+ ];
116
+ function publish(type, properties) {
117
+ const msg = {
118
+ type,
119
+ properties,
120
+ };
121
+ buffer.push(msg);
122
+ const json = JSON.stringify(msg);
123
+ [...sockets.values()].map((s) => s.send(json));
124
+ }
107
125
  wss2.on("connection", (socket, req) => {
108
126
  sockets.add(socket);
127
+ for (const msg of buffer) {
128
+ socket.send(JSON.stringify(msg));
129
+ }
109
130
  socket.on("close", () => {
110
131
  sockets.delete(socket);
111
132
  });
133
+ socket.on("message", (data) => {
134
+ const parsed = JSON.parse(data.toString());
135
+ if (parsed.type === "log.cleared") {
136
+ buffer = buffer.filter((msg) => msg.properties?.functionID !== parsed.properties?.functionID);
137
+ }
138
+ });
112
139
  });
113
140
  wss.on("connection", (socket, req) => {
114
141
  if (req.headers.origin?.endsWith("localhost:3000"))
@@ -179,13 +206,6 @@ export async function useLocalServer(opts) {
179
206
  cb(func);
180
207
  });
181
208
  }
182
- function publish(type, properties) {
183
- const msg = JSON.stringify({
184
- type,
185
- properties,
186
- });
187
- [...sockets.values()].map((s) => s.send(msg));
188
- }
189
209
  bus.subscribe("function.invoked", async (evt) => {
190
210
  publish("function.invoked", evt.properties);
191
211
  updateFunction(evt.properties.functionID, (draft) => {
@@ -1,6 +1,12 @@
1
1
  import { IHostedZone } from "aws-cdk-lib/aws-route53";
2
2
  import { ErrorResponse, DistributionProps, BehaviorOptions, IOrigin } from "aws-cdk-lib/aws-cloudfront";
3
3
  import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
4
+ export interface BaseSiteFileOptions {
5
+ exclude: string | string[];
6
+ include: string | string[];
7
+ cacheControl: string;
8
+ contentType?: string;
9
+ }
4
10
  /**
5
11
  * The customDomain for this website. SST supports domains that are hosted either on [Route 53](https://aws.amazon.com/route53/) or externally.
6
12
  *
@@ -13,24 +13,14 @@ import { Size as CDKSize, Duration as CDKDuration } from "aws-cdk-lib/core";
13
13
  declare const supportedRuntimes: {
14
14
  container: CDKRuntime;
15
15
  rust: CDKRuntime;
16
- nodejs: CDKRuntime;
17
- "nodejs4.3": CDKRuntime;
18
- "nodejs6.10": CDKRuntime;
19
- "nodejs8.10": CDKRuntime;
20
- "nodejs10.x": CDKRuntime;
21
16
  "nodejs12.x": CDKRuntime;
22
17
  "nodejs14.x": CDKRuntime;
23
18
  "nodejs16.x": CDKRuntime;
24
19
  "nodejs18.x": CDKRuntime;
25
- "python2.7": CDKRuntime;
26
- "python3.6": CDKRuntime;
27
20
  "python3.7": CDKRuntime;
28
21
  "python3.8": CDKRuntime;
29
22
  "python3.9": CDKRuntime;
30
23
  "python3.10": CDKRuntime;
31
- "dotnetcore1.0": CDKRuntime;
32
- "dotnetcore2.0": CDKRuntime;
33
- "dotnetcore2.1": CDKRuntime;
34
24
  "dotnetcore3.1": CDKRuntime;
35
25
  dotnet6: CDKRuntime;
36
26
  java8: CDKRuntime;
@@ -66,6 +56,10 @@ export interface FunctionProps extends Omit<FunctionOptions, "functionName" | "m
66
56
  *```
67
57
  */
68
58
  copyFiles?: FunctionCopyFilesProps[];
59
+ /**
60
+ * Used to configure go function properties
61
+ */
62
+ go?: GoProps;
69
63
  /**
70
64
  * Used to configure nodejs function properties
71
65
  */
@@ -458,6 +452,47 @@ export interface PythonProps {
458
452
  */
459
453
  installCommands?: string[];
460
454
  }
455
+ /**
456
+ * Used to configure Go bundling options
457
+ */
458
+ export interface GoProps {
459
+ /**
460
+ * The ldflags to use when building the Go module.
461
+ *
462
+ * @default ["-s", "-w"]
463
+ * @example
464
+ * ```js
465
+ * go: {
466
+ * ldFlags: ["-X main.version=1.0.0"],
467
+ * }
468
+ * ```
469
+ */
470
+ ldFlags?: string[];
471
+ /**
472
+ * The build tags to use when building the Go module.
473
+ *
474
+ * @default []
475
+ * @example
476
+ * ```js
477
+ * go: {
478
+ * buildTags: ["enterprise", "pro"],
479
+ * }
480
+ * ```
481
+ */
482
+ buildTags?: string[];
483
+ /**
484
+ * Whether to enable CGO for the Go build.
485
+ *
486
+ * @default false
487
+ * @example
488
+ * ```js
489
+ * go: {
490
+ * cgoEnabled: true,
491
+ * }
492
+ * ```
493
+ */
494
+ cgoEnabled?: boolean;
495
+ }
461
496
  /**
462
497
  * Used to configure Java package build options
463
498
  */
@@ -27,24 +27,14 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
27
27
  const supportedRuntimes = {
28
28
  container: CDKRuntime.FROM_IMAGE,
29
29
  rust: CDKRuntime.PROVIDED_AL2,
30
- nodejs: CDKRuntime.NODEJS,
31
- "nodejs4.3": CDKRuntime.NODEJS_4_3,
32
- "nodejs6.10": CDKRuntime.NODEJS_6_10,
33
- "nodejs8.10": CDKRuntime.NODEJS_8_10,
34
- "nodejs10.x": CDKRuntime.NODEJS_10_X,
35
30
  "nodejs12.x": CDKRuntime.NODEJS_12_X,
36
31
  "nodejs14.x": CDKRuntime.NODEJS_14_X,
37
32
  "nodejs16.x": CDKRuntime.NODEJS_16_X,
38
33
  "nodejs18.x": CDKRuntime.NODEJS_18_X,
39
- "python2.7": CDKRuntime.PYTHON_2_7,
40
- "python3.6": CDKRuntime.PYTHON_3_6,
41
34
  "python3.7": CDKRuntime.PYTHON_3_7,
42
35
  "python3.8": CDKRuntime.PYTHON_3_8,
43
36
  "python3.9": CDKRuntime.PYTHON_3_9,
44
37
  "python3.10": CDKRuntime.PYTHON_3_10,
45
- "dotnetcore1.0": CDKRuntime.DOTNET_CORE_1,
46
- "dotnetcore2.0": CDKRuntime.DOTNET_CORE_2,
47
- "dotnetcore2.1": CDKRuntime.DOTNET_CORE_2_1,
48
38
  "dotnetcore3.1": CDKRuntime.DOTNET_CORE_3_1,
49
39
  dotnet6: CDKRuntime.DOTNET_6,
50
40
  java8: CDKRuntime.JAVA_8,
@@ -72,9 +72,11 @@ export class RemixSite extends SsrSite {
72
72
  // appropriate Lambda@Edge handler. We will utilise an internal asset
73
73
  // template to create this wrapper within the "core server build" output
74
74
  // directory.
75
+ // Ensure build directory exists
76
+ const buildPath = path.join(this.props.path, "build");
77
+ fs.mkdirSync(buildPath, { recursive: true });
75
78
  // Copy the server lambda handler
76
- const handler = path.join(this.props.path, "build", "server.js");
77
- fs.copyFileSync(path.resolve(__dirname, `../support/remix-site-function/${wrapperFile}`), handler);
79
+ fs.copyFileSync(path.resolve(__dirname, `../support/remix-site-function/${wrapperFile}`), path.join(buildPath, "server.js"));
78
80
  // Copy the Remix polyfil to the server build directory
79
81
  //
80
82
  // Note: We need to ensure that the polyfills are injected above other code that
@@ -82,10 +84,10 @@ export class RemixSite extends SsrSite {
82
84
  // doesn't appear to guarantee this, we therefore leverage ESBUild's
83
85
  // `inject` option to ensure that the polyfills are injected at the top of
84
86
  // the bundle.
85
- const polyfillDest = path.join(this.props.path, "build/polyfill.js");
87
+ const polyfillDest = path.join(buildPath, "polyfill.js");
86
88
  fs.copyFileSync(path.resolve(__dirname, "../support/remix-site-function/polyfill.js"), polyfillDest);
87
89
  return {
88
- handler: path.join(this.props.path, "build", "server.handler"),
90
+ handler: path.join(buildPath, "server.handler"),
89
91
  esbuild: { inject: [polyfillDest] },
90
92
  };
91
93
  }
@@ -8,7 +8,7 @@ import { SSTConstruct } from "./Construct.js";
8
8
  import { NodeJSProps } from "./Function.js";
9
9
  import { SsrFunction } from "./SsrFunction.js";
10
10
  import { EdgeFunction } from "./EdgeFunction.js";
11
- import { BaseSiteDomainProps, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
11
+ import { BaseSiteFileOptions, BaseSiteDomainProps, 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";
@@ -27,6 +27,8 @@ export interface SsrSiteNodeJSProps extends NodeJSProps {
27
27
  }
28
28
  export interface SsrDomainProps extends BaseSiteDomainProps {
29
29
  }
30
+ export interface SsrSiteFileOptions extends BaseSiteFileOptions {
31
+ }
30
32
  export interface SsrSiteReplaceProps extends BaseSiteReplaceProps {
31
33
  }
32
34
  export interface SsrCdkDistributionProps extends BaseSiteCdkDistributionProps {
@@ -192,6 +194,60 @@ export interface SsrSiteProps {
192
194
  responseHeadersPolicy?: IResponseHeadersPolicy;
193
195
  server?: Pick<FunctionProps, "vpc" | "vpcSubnets" | "securityGroups" | "allowAllOutbound" | "allowPublicSubnet" | "architecture" | "logRetention">;
194
196
  };
197
+ /**
198
+ * Pass in a list of file options to customize cache control and content type specific files.
199
+ *
200
+ * @default Versioned files cached for 1 year at the CDN and brower level. Unversioned files cached for 1 year at the CDN level, but not at the browser level.
201
+ * ```js
202
+ * [
203
+ * {
204
+ * exclude: "*",
205
+ * include: "{versioned_directory}/*",
206
+ * cacheControl: "public,max-age=31536000,immutable",
207
+ * },
208
+ * {
209
+ * exclude: "*",
210
+ * include: "[{non_versioned_file1}, {non_versioned_file2}, ...]",
211
+ * cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
212
+ * },
213
+ * {
214
+ * exclude: "*",
215
+ * include: "[{non_versioned_dir_1}/*, {non_versioned_dir_2}/*, ...]",
216
+ * cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
217
+ * },
218
+ * ]
219
+ * ```
220
+ *
221
+ * @example
222
+ * ```js
223
+ * new AstroSite(stack, "Site", {
224
+ * fileOptions: [
225
+ * {
226
+ * exclude: "*",
227
+ * include: "{versioned_directory}/*.css",
228
+ * cacheControl: "public,max-age=31536000,immutable",
229
+ * contentType: "text/css; charset=UTF-8",
230
+ * },
231
+ * {
232
+ * exclude: "*",
233
+ * include: "[{versioned_directory}/*.js]",
234
+ * cacheControl: "public,max-age=31536000,immutable",
235
+ * },
236
+ * {
237
+ * exclude: "*",
238
+ * include: "[{non_versioned_file1}, {non_versioned_file2}, ...]",
239
+ * cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
240
+ * },
241
+ * {
242
+ * exclude: "*",
243
+ * include: "[{non_versioned_dir_1}/*, {non_versioned_dir_2}/*, ...]",
244
+ * cacheControl: "public,max-age=0,s-maxage=31536000,must-revalidate",
245
+ * },
246
+ * ]
247
+ * });
248
+ * ```
249
+ */
250
+ fileOptions?: SsrSiteFileOptions[];
195
251
  }
196
252
  type SsrSiteNormalizedProps = SsrSiteProps & {
197
253
  path: Exclude<SsrSiteProps["path"], undefined>;
@@ -348,12 +348,14 @@ export class SsrSite extends Construct {
348
348
  return assets;
349
349
  }
350
350
  createS3AssetFileOptions() {
351
+ if (this.props.fileOptions)
352
+ return this.props.fileOptions;
351
353
  // Build file options
352
354
  const fileOptions = [];
353
355
  const clientPath = path.join(this.props.path, this.buildConfig.clientBuildOutputDir);
354
356
  for (const item of fs.readdirSync(clientPath)) {
355
357
  // Versioned files will be cached for 1 year (immutable) both at
356
- // CDN and browser level.
358
+ // the CDN and browser level.
357
359
  if (item === this.buildConfig.clientBuildVersionedSubDir) {
358
360
  fileOptions.push({
359
361
  exclude: "*",
@@ -425,15 +427,19 @@ export class SsrSite extends Construct {
425
427
  ObjectKey: asset.s3ObjectKey,
426
428
  })),
427
429
  DestinationBucketName: this.bucket.bucketName,
428
- FileOptions: (fileOptions || []).map(({ exclude, include, cacheControl }) => {
430
+ FileOptions: (fileOptions || []).map(({ exclude, include, cacheControl, contentType }) => {
431
+ if (typeof exclude === "string") {
432
+ exclude = [exclude];
433
+ }
434
+ if (typeof include === "string") {
435
+ include = [include];
436
+ }
429
437
  return [
430
- "--exclude",
431
- exclude,
432
- "--include",
433
- include,
434
- "--cache-control",
435
- cacheControl,
436
- ];
438
+ ...exclude.map((per) => ["--exclude", per]),
439
+ ...include.map((per) => ["--include", per]),
440
+ ["--cache-control", cacheControl],
441
+ contentType ? ["--content-type", contentType] : [],
442
+ ].flat();
437
443
  }),
438
444
  ReplaceValues: this.getS3ContentReplaceValues(),
439
445
  },
@@ -3,15 +3,9 @@ import { Bucket, BucketProps, IBucket } from "aws-cdk-lib/aws-s3";
3
3
  import { ICertificate } from "aws-cdk-lib/aws-certificatemanager";
4
4
  import { IHostedZone } from "aws-cdk-lib/aws-route53";
5
5
  import { Distribution, IDistribution } from "aws-cdk-lib/aws-cloudfront";
6
- import { BaseSiteDomainProps, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
6
+ import { BaseSiteDomainProps, BaseSiteFileOptions, BaseSiteReplaceProps, BaseSiteCdkDistributionProps } from "./BaseSite.js";
7
7
  import { SSTConstruct } from "./Construct.js";
8
8
  import { FunctionBindingProps } from "./util/functionBinding.js";
9
- export interface StaticSiteFileOptions {
10
- exclude: string | string[];
11
- include: string | string[];
12
- cacheControl: string;
13
- contentType?: string;
14
- }
15
9
  export interface StaticSiteProps {
16
10
  /**
17
11
  * Path to the directory where the website source is located.
@@ -77,7 +71,7 @@ export interface StaticSiteProps {
77
71
  /**
78
72
  * 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.
79
73
  *
80
- * Defaults to no cache control for HTML files, and a 1 year cache control for JS/CSS files.
74
+ * @default No cache control for HTML files, and a 1 year cache control for JS/CSS files.
81
75
  * ```js
82
76
  * [
83
77
  * {
@@ -274,6 +268,8 @@ export interface StaticSiteProps {
274
268
  }
275
269
  export interface StaticSiteDomainProps extends BaseSiteDomainProps {
276
270
  }
271
+ export interface StaticSiteFileOptions extends BaseSiteFileOptions {
272
+ }
277
273
  export interface StaticSiteReplaceProps extends BaseSiteReplaceProps {
278
274
  }
279
275
  export interface StaticSiteCdkDistributionProps extends BaseSiteCdkDistributionProps {
@@ -195,6 +195,9 @@ function permissionsToStatementsAndGrants(permissions) {
195
195
  if (secret) {
196
196
  statements.push(buildPolicyStatement(["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"], [secret.secretArn]));
197
197
  }
198
+ if (secret?.encryptionKey) {
199
+ statements.push(buildPolicyStatement(["kms:Decrypt"], [secret.encryptionKey.keyArn]));
200
+ }
198
201
  }
199
202
  ////////////////////////////////////
200
203
  // Case: grant method
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sideEffects": false,
3
3
  "name": "sst",
4
- "version": "2.21.7",
4
+ "version": "2.22.0",
5
5
  "bin": {
6
6
  "sst": "cli/sst.js"
7
7
  },
@@ -55,11 +55,20 @@ export const useGoHandler = Context.memo(async () => {
55
55
  const project = await find(parsed.dir, "go.mod");
56
56
  sources.set(input.functionID, project);
57
57
  const src = path.relative(project, input.props.handler);
58
+ const ldFlags = input.props.go?.ldFlags || ["-s", "-w"];
59
+ const buildTags = input.props.go?.buildTags || [];
58
60
  if (input.mode === "start") {
59
61
  try {
60
62
  const target = path.join(input.out, handlerName);
61
63
  const srcPath = os.platform() === "win32" ? src.replaceAll("\\", "\\\\") : src;
62
- const result = await execAsync(`go build -ldflags "-s -w" -o "${target}" ./${srcPath}`, {
64
+ const result = await execAsync([
65
+ "go",
66
+ "build",
67
+ ...(ldFlags ? [`-ldflags "${ldFlags.join(" ")}"`] : []),
68
+ ...(buildTags ? [`-t ${buildTags.join(" ")}`] : []),
69
+ `-o "${target}"`,
70
+ `./${srcPath}`,
71
+ ].join(" "), {
63
72
  cwd: project,
64
73
  env: {
65
74
  ...process.env,
@@ -77,11 +86,18 @@ export const useGoHandler = Context.memo(async () => {
77
86
  try {
78
87
  const target = path.join(input.out, "bootstrap");
79
88
  const srcPath = os.platform() === "win32" ? src.replaceAll("\\", "\\\\") : src;
80
- await execAsync(`go build -ldflags "-s -w" -o "${target}" ./${srcPath}`, {
89
+ await execAsync([
90
+ "go",
91
+ "build",
92
+ ...(ldFlags ? [`-ldflags "${ldFlags.join(" ")}"`] : []),
93
+ ...(buildTags ? [`-t ${buildTags.join(" ")}`] : []),
94
+ `-o "${target}"`,
95
+ `./${srcPath}`,
96
+ ].join(" "), {
81
97
  cwd: project,
82
98
  env: {
83
99
  ...process.env,
84
- CGO_ENABLED: "0",
100
+ CGO_ENABLED: input.props.go?.cgoEnabled ? "1" : "0",
85
101
  GOARCH: input.props.architecture === "arm_64" ? "arm64" : "amd64",
86
102
  GOOS: "linux",
87
103
  },
@@ -3,7 +3,6 @@ import fs from "fs/promises";
3
3
  import { useRuntimeHandlers } from "../handlers.js";
4
4
  import { useRuntimeWorkers } from "../workers.js";
5
5
  import { Context } from "../../context/context.js";
6
- import { VisibleError } from "../../error.js";
7
6
  import { exec, spawn } from "child_process";
8
7
  import { promisify } from "util";
9
8
  import { useRuntimeServerConfig } from "../server.js";
@@ -66,7 +65,7 @@ export const useRustHandler = Context.memo(async () => {
66
65
  sources.set(input.functionID, project);
67
66
  if (input.mode === "start") {
68
67
  try {
69
- await execAsync(`cargo build --bin ${parsed.name}`, {
68
+ await execAsync(["cargo", "build", `--bin ${parsed.name}`].join(" "), {
70
69
  cwd: project,
71
70
  env: {
72
71
  ...process.env,
@@ -75,12 +74,22 @@ export const useRustHandler = Context.memo(async () => {
75
74
  await fs.cp(path.join(project, `target/debug`, parsed.name), path.join(input.out, "handler"));
76
75
  }
77
76
  catch (ex) {
78
- throw new VisibleError("Failed to build");
77
+ return {
78
+ type: "error",
79
+ errors: [String(ex)],
80
+ };
79
81
  }
80
82
  }
81
83
  if (input.mode === "deploy") {
82
84
  try {
83
- await execAsync(`cargo lambda build --release --bin ${parsed.name}`, {
85
+ await execAsync([
86
+ "cargo",
87
+ "lambda",
88
+ "build",
89
+ "--release",
90
+ ...(input.props.architecture === "arm_64" ? ["--arm64"] : []),
91
+ `--bin ${parsed.name}`,
92
+ ].join(" "), {
84
93
  cwd: project,
85
94
  env: {
86
95
  ...process.env,
@@ -89,7 +98,10 @@ export const useRustHandler = Context.memo(async () => {
89
98
  await fs.cp(path.join(project, `target/lambda/`, parsed.name, "bootstrap"), path.join(input.out, "bootstrap"));
90
99
  }
91
100
  catch (ex) {
92
- throw new VisibleError("Failed to build");
101
+ return {
102
+ type: "error",
103
+ errors: [String(ex)],
104
+ };
93
105
  }
94
106
  }
95
107
  return {
package/sst.mjs CHANGED
@@ -5660,12 +5660,21 @@ var init_go = __esm({
5660
5660
  const project = await find(parsed.dir, "go.mod");
5661
5661
  sources.set(input.functionID, project);
5662
5662
  const src = path11.relative(project, input.props.handler);
5663
+ const ldFlags = input.props.go?.ldFlags || ["-s", "-w"];
5664
+ const buildTags = input.props.go?.buildTags || [];
5663
5665
  if (input.mode === "start") {
5664
5666
  try {
5665
5667
  const target = path11.join(input.out, handlerName);
5666
5668
  const srcPath = os4.platform() === "win32" ? src.replaceAll("\\", "\\\\") : src;
5667
5669
  const result = await execAsync(
5668
- `go build -ldflags "-s -w" -o "${target}" ./${srcPath}`,
5670
+ [
5671
+ "go",
5672
+ "build",
5673
+ ...ldFlags ? [`-ldflags "${ldFlags.join(" ")}"`] : [],
5674
+ ...buildTags ? [`-t ${buildTags.join(" ")}`] : [],
5675
+ `-o "${target}"`,
5676
+ `./${srcPath}`
5677
+ ].join(" "),
5669
5678
  {
5670
5679
  cwd: project,
5671
5680
  env: {
@@ -5685,12 +5694,19 @@ var init_go = __esm({
5685
5694
  const target = path11.join(input.out, "bootstrap");
5686
5695
  const srcPath = os4.platform() === "win32" ? src.replaceAll("\\", "\\\\") : src;
5687
5696
  await execAsync(
5688
- `go build -ldflags "-s -w" -o "${target}" ./${srcPath}`,
5697
+ [
5698
+ "go",
5699
+ "build",
5700
+ ...ldFlags ? [`-ldflags "${ldFlags.join(" ")}"`] : [],
5701
+ ...buildTags ? [`-t ${buildTags.join(" ")}`] : [],
5702
+ `-o "${target}"`,
5703
+ `./${srcPath}`
5704
+ ].join(" "),
5689
5705
  {
5690
5706
  cwd: project,
5691
5707
  env: {
5692
5708
  ...process.env,
5693
- CGO_ENABLED: "0",
5709
+ CGO_ENABLED: input.props.go?.cgoEnabled ? "1" : "0",
5694
5710
  GOARCH: input.props.architecture === "arm_64" ? "arm64" : "amd64",
5695
5711
  GOOS: "linux"
5696
5712
  }
@@ -5993,7 +6009,6 @@ var init_rust = __esm({
5993
6009
  init_handlers2();
5994
6010
  init_workers();
5995
6011
  init_context();
5996
- init_error();
5997
6012
  init_server();
5998
6013
  init_fs();
5999
6014
  execAsync2 = promisify2(exec4);
@@ -6054,34 +6069,53 @@ var init_rust = __esm({
6054
6069
  sources.set(input.functionID, project);
6055
6070
  if (input.mode === "start") {
6056
6071
  try {
6057
- await execAsync2(`cargo build --bin ${parsed.name}`, {
6058
- cwd: project,
6059
- env: {
6060
- ...process.env
6072
+ await execAsync2(
6073
+ ["cargo", "build", `--bin ${parsed.name}`].join(" "),
6074
+ {
6075
+ cwd: project,
6076
+ env: {
6077
+ ...process.env
6078
+ }
6061
6079
  }
6062
- });
6080
+ );
6063
6081
  await fs11.cp(
6064
6082
  path12.join(project, `target/debug`, parsed.name),
6065
6083
  path12.join(input.out, "handler")
6066
6084
  );
6067
6085
  } catch (ex) {
6068
- throw new VisibleError("Failed to build");
6086
+ return {
6087
+ type: "error",
6088
+ errors: [String(ex)]
6089
+ };
6069
6090
  }
6070
6091
  }
6071
6092
  if (input.mode === "deploy") {
6072
6093
  try {
6073
- await execAsync2(`cargo lambda build --release --bin ${parsed.name}`, {
6074
- cwd: project,
6075
- env: {
6076
- ...process.env
6094
+ await execAsync2(
6095
+ [
6096
+ "cargo",
6097
+ "lambda",
6098
+ "build",
6099
+ "--release",
6100
+ ...input.props.architecture === "arm_64" ? ["--arm64"] : [],
6101
+ `--bin ${parsed.name}`
6102
+ ].join(" "),
6103
+ {
6104
+ cwd: project,
6105
+ env: {
6106
+ ...process.env
6107
+ }
6077
6108
  }
6078
- });
6109
+ );
6079
6110
  await fs11.cp(
6080
6111
  path12.join(project, `target/lambda/`, parsed.name, "bootstrap"),
6081
6112
  path12.join(input.out, "bootstrap")
6082
6113
  );
6083
6114
  } catch (ex) {
6084
- throw new VisibleError("Failed to build");
6115
+ return {
6116
+ type: "error",
6117
+ errors: [String(ex)]
6118
+ };
6085
6119
  }
6086
6120
  }
6087
6121
  return {
@@ -6661,7 +6695,11 @@ import {
6661
6695
  RemovalPolicy
6662
6696
  } from "aws-cdk-lib/core";
6663
6697
  import { Function, Runtime as Runtime2, Code } from "aws-cdk-lib/aws-lambda";
6664
- import { PolicyStatement } from "aws-cdk-lib/aws-iam";
6698
+ import {
6699
+ ManagedPolicy,
6700
+ PermissionsBoundary,
6701
+ PolicyStatement
6702
+ } from "aws-cdk-lib/aws-iam";
6665
6703
  import { Rule } from "aws-cdk-lib/aws-events";
6666
6704
  import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
6667
6705
  import {
@@ -6678,19 +6716,20 @@ async function loadCDKStatus() {
6678
6716
  new DescribeStacksCommand2({ StackName: stackName })
6679
6717
  );
6680
6718
  if (!stacks || stacks.length === 0)
6681
- return false;
6719
+ return { status: "bootstrap" };
6682
6720
  if (!["CREATE_COMPLETE", "UPDATE_COMPLETE"].includes(stacks[0].StackStatus)) {
6683
- return false;
6721
+ return { status: "bootstrap" };
6684
6722
  }
6685
6723
  const output = stacks[0].Outputs?.find(
6686
6724
  (o) => o.OutputKey === "BootstrapVersion"
6687
6725
  );
6688
- if (!output || parseInt(output.OutputValue) < 14)
6689
- return false;
6690
- return true;
6726
+ if (!output || parseInt(output.OutputValue) < 14) {
6727
+ return { status: "update" };
6728
+ }
6729
+ return { status: "ready" };
6691
6730
  } catch (e) {
6692
6731
  if (e.name === "ValidationError" && e.message === `Stack with id ${stackName} does not exist`) {
6693
- return false;
6732
+ return { status: "bootstrap" };
6694
6733
  } else {
6695
6734
  throw e;
6696
6735
  }
@@ -6709,7 +6748,7 @@ async function loadSSTStatus() {
6709
6748
  );
6710
6749
  } catch (e) {
6711
6750
  if (e.Code === "ValidationError" && e.message === `Stack with id ${stackName} does not exist`) {
6712
- return null;
6751
+ return { status: "bootstrap" };
6713
6752
  }
6714
6753
  throw e;
6715
6754
  }
@@ -6723,7 +6762,7 @@ async function loadSSTStatus() {
6723
6762
  }
6724
6763
  });
6725
6764
  if (!version2 || !bucket) {
6726
- return null;
6765
+ return { status: "bootstrap" };
6727
6766
  }
6728
6767
  const latestParts = LATEST_VERSION.split(".");
6729
6768
  const latestMajor = parseInt(latestParts[0]);
@@ -6732,9 +6771,9 @@ async function loadSSTStatus() {
6732
6771
  const currentMajor = parseInt(currentParts[0]);
6733
6772
  const currentMinor = parseInt(currentParts[1] || "0");
6734
6773
  if (currentMajor < latestMajor || currentMajor > latestMajor || currentMinor < latestMinor) {
6735
- return null;
6774
+ return { status: "update" };
6736
6775
  }
6737
- return { version: version2, bucket };
6776
+ return { status: "ready", version: version2, bucket };
6738
6777
  }
6739
6778
  async function bootstrapSST() {
6740
6779
  const { region, bootstrap: bootstrap2, cdk } = useProject().config;
@@ -6815,6 +6854,14 @@ async function bootstrapSST() {
6815
6854
  }
6816
6855
  });
6817
6856
  rule.addTarget(new LambdaFunction(fn));
6857
+ if (cdk?.customPermissionsBoundary) {
6858
+ const boundaryPolicy = ManagedPolicy.fromManagedPolicyName(
6859
+ stack,
6860
+ "PermissionBoundaryPolicy",
6861
+ cdk.customPermissionsBoundary
6862
+ );
6863
+ PermissionsBoundary.of(stack).apply(boundaryPolicy);
6864
+ }
6818
6865
  new CfnOutput(stack, OUTPUT_VERSION, { value: LATEST_VERSION });
6819
6866
  new CfnOutput(stack, OUTPUT_BUCKET, { value: bucket.bucketName });
6820
6867
  const asm = app.synth();
@@ -6904,11 +6951,11 @@ var init_bootstrap = __esm({
6904
6951
  loadSSTStatus()
6905
6952
  ]);
6906
6953
  Logger.debug("Loaded bootstrap status");
6907
- const needToBootstrapCDK = !cdkStatus;
6908
- const needToBootstrapSST = !sstStatus;
6954
+ const needToBootstrapCDK = cdkStatus.status !== "ready";
6955
+ const needToBootstrapSST = sstStatus.status !== "ready";
6909
6956
  if (needToBootstrapCDK || needToBootstrapSST) {
6910
6957
  const spinner = createSpinner(
6911
- "Deploying bootstrap stack, this only needs to happen once"
6958
+ cdkStatus.status === "bootstrap" || sstStatus.status === "bootstrap" ? "Deploying bootstrap stack, this only needs to happen once" : "Updating bootstrap stack"
6912
6959
  ).start();
6913
6960
  if (needToBootstrapCDK) {
6914
6961
  await bootstrapCDK();
@@ -6916,7 +6963,7 @@ var init_bootstrap = __esm({
6916
6963
  if (needToBootstrapSST) {
6917
6964
  await bootstrapSST();
6918
6965
  sstStatus = await loadSSTStatus();
6919
- if (!sstStatus)
6966
+ if (sstStatus.status !== "ready")
6920
6967
  throw new VisibleError("Failed to load bootstrap stack status");
6921
6968
  }
6922
6969
  spinner.succeed();
@@ -7085,11 +7132,40 @@ async function useLocalServer(opts) {
7085
7132
  const wss = new WebSocketServer({ noServer: true });
7086
7133
  const wss2 = new WebSocketServer({ noServer: true });
7087
7134
  const sockets = /* @__PURE__ */ new Set();
7135
+ let buffer = [
7136
+ {
7137
+ type: "cli.dev",
7138
+ properties: {
7139
+ stage: project.config.stage,
7140
+ app: project.config.name
7141
+ }
7142
+ }
7143
+ ];
7144
+ function publish(type, properties) {
7145
+ const msg = {
7146
+ type,
7147
+ properties
7148
+ };
7149
+ buffer.push(msg);
7150
+ const json = JSON.stringify(msg);
7151
+ [...sockets.values()].map((s) => s.send(json));
7152
+ }
7088
7153
  wss2.on("connection", (socket, req) => {
7089
7154
  sockets.add(socket);
7155
+ for (const msg of buffer) {
7156
+ socket.send(JSON.stringify(msg));
7157
+ }
7090
7158
  socket.on("close", () => {
7091
7159
  sockets.delete(socket);
7092
7160
  });
7161
+ socket.on("message", (data2) => {
7162
+ const parsed = JSON.parse(data2.toString());
7163
+ if (parsed.type === "log.cleared") {
7164
+ buffer = buffer.filter(
7165
+ (msg) => msg.properties?.functionID !== parsed.properties?.functionID
7166
+ );
7167
+ }
7168
+ });
7093
7169
  });
7094
7170
  wss.on("connection", (socket, req) => {
7095
7171
  if (req.headers.origin?.endsWith("localhost:3000"))
@@ -7160,13 +7236,6 @@ async function useLocalServer(opts) {
7160
7236
  cb(func);
7161
7237
  });
7162
7238
  }
7163
- function publish(type, properties) {
7164
- const msg = JSON.stringify({
7165
- type,
7166
- properties
7167
- });
7168
- [...sockets.values()].map((s) => s.send(msg));
7169
- }
7170
7239
  bus.subscribe("function.invoked", async (evt) => {
7171
7240
  publish("function.invoked", evt.properties);
7172
7241
  updateFunction(evt.properties.functionID, (draft) => {